<commit_before>#!/usr/bin/env python

import struct

from .compat import *
from .util import *

class ScriptData:
    NUMBER, BOOLEAN, STRING, OBJECT, RESERVED, NULL, \
    UNDEFINED, REFERENCE, ECMAARRAY, OBJECTEND, STRICTARRAY, \
    DATE, LONGSTRING = range(13)

    class Object(OrderedDict):
        pass

    class ECMAArray(OrderedDict):
        pass

    class ObjectEnd(IOError):
        pass

    class Date(object):
        def __init__(self, timestamp, offset):
            self.timestamp = timestamp
            self.offset = offset

class Packet(object):
    @classmethod
    def _deserialize(cls):
        raise NotImplementedError

    @classmethod
    def deserialize(cls, fd=None, io=None):
        if not io:
            if not fd:
                raise IOError("Missing fd parameter")

            io = PacketIO(fd)

        return cls._deserialize(io)

    def _serialize(self):
        raise NotImplementedError

    def serialize(self, packet=None):
        if not packet:
            packet = PacketIO()

        self._serialize(packet)

        return packet.getvalue()

    def __bytes__(self):
        return self.serialize()

#    __str__ = __bytes__

class PacketIO(object):
    def __init__(self, fd=None):
        if fd:
            self.fd = fd
        else:
            self.io = BytesIO()

        self.data_left = None
        self._objects = []

    def getvalue(self):
        return self.io.getvalue()

    def read(self, size=None):
        if self.data_left is not None:
            if size is None:
                size = self.data_left

            if size > self.data_left:
                raise IOError

            self.data_left -= size

        return self.fd.read(size)

    @property
    def written(self):
        return len(self.getvalue())

    def write(self, data):
        return self.io.write(data)

    # Primitives

    def write_u8(self, num):
        return self.write(struct.pack("B", int(num)))

    def write_u16(self, num):
        return self.write(struct.pack(">H", int(num)))

    def write_s16(self, num):
        return self.write(struct.pack(">h", int(num)))

    def write_u24(self, num):
        ret = struct.pack(">I", int(num))
        return self.write(ret[1:])

    def write_s24(self, num):
        ret = struct.pack(">i", int(num))
        return self.write(ret[1:])

    def write_s32(self, num):
        return self.write(struct.pack(">i", int(num)))

    def write_u32(self, num):
        return self.write(struct.pack(">I", int(num)))

    def write_s32e(self, num):
        ret = struct.pack(">i", int(num))
        return self.write(ret[1:] + byte(ret[0]))

    def write_u64(self, num):
        return self.write(struct.pack(">Q", int(num)))

    def write_s8_8(self, num):
        num = float(num) * float(2**8)
        return self.write_s16(int(num))

    def write_s16_16(self, num):
        num = float(num) * float(2**16)
        return self.write_s32(int(num))

    def write_u3264(self, version, num):
        if version == 1:
            self.write_u64(num)
        else:
            self.write_u32(num)

    def write_double(self, num):
        return self.write(struct.pack(">d", num))

    def write_string(self, string):
        string = bytes(string, "utf8")
        return self.write(string + b"\x00")

    def write_padded(self, value, length, padding=b" "):
        for i in range(length):
            try:
                v = value[i]
                self.write(bytes(v, "ascii"))
            except IndexError:
                self.write(padding)

    def read_u8(self):
        try:
            ret = struct.unpack("B", self.read(1))[0]
        except struct.error:
            raise IOError

        return ret

    def read_u16(self):
        try:
            ret = struct.unpack(">H", self.read(2))[0]
        except struct.error:
            raise IOError

        return ret

    def read_s16(self):
        try:
            ret = struct.unpack(">h", self.read(2))[0]
        except struct.error:
            raise IOError

        return ret

    def read_s8_8(self):
        return float(self.read_s16()) / float(2**8)

    def read_s16_16(self):
        return float(self.read_s32()) / float(2**16)

    def read_s24(self):
        try:
            high, low = struct.unpack(">Bh", self.read(3))
        except struct.error:
            raise IOError

        ret = (high << 16) + low

        return ret

    def read_u24(self):
        try:
            high, low = struct.unpack(">BH", self.read(3))
        except struct.error:
            raise IOError

        ret = (high << 16) + low

        return ret

    def read_s32(self):
        try:
            ret = struct.unpack(">i", self.read(4))[0]
        except struct.error:
            raise IOError

        return ret

    def read_u32(self):
        try:
            ret = struct.unpack(">I", self.read(4))[0]
        except struct.error:
            raise IOError

        return ret

    def read_s32e(self):
        low_high = self.read(4)

        if len(low_high) > 4:
            raise IOError

        combined = byte(low_high[3]) + low_high[:3]

        try:
            ret = struct.unpack(">i", combined)[0]
        except struct.error:
            raise IOError

        return ret

    def read_u3264(self, version):
        if version == 1:
            return self.read_u64()
        else:
            return self.read_u32()

    def read_u64(self):
        try:
            ret = struct.unpack(">Q", self.read(8))[0]
        except struct.error:
            raise IOError

        return ret

    def read_double(self):
        try:
            ret = struct.unpack(">d", self.read(8))[0]
        except struct.error:
            raise IOError

        return ret

    def read_string(self):
        ret = b""

        while True:
            try:
                ch = self.read(1)
            except IOError:
                break

            if ord(ch) == 0:
                break

            ret += ch

        return str(ret, "utf8")

    def read_padded(self, length):
        return str(self.read(length), "ascii").rstrip()

    # ScriptData values

    def read_script_value(self):
        typ = self.read_u8()

        if typ == ScriptData.NUMBER:
            return self.read_script_number()

        elif typ == ScriptData.BOOLEAN:
            return self.read_script_boolean()

        elif typ == ScriptData.STRING:
            return self.read_script_string()

        elif typ == ScriptData.OBJECT:
            return self.read_script_object(ScriptData.Object())

        elif typ == ScriptData.NULL or typ == ScriptData.UNDEFINED:
            return None

        elif typ == ScriptData.REFERENCE:
            ref = self.read_u16()

            return self._objects[ref]

        elif typ == ScriptData.ECMAARRAY:
            container = ScriptData.ECMAArray()
            container.length = self.read_u32()

            return self.read_script_object(container)

        elif typ == ScriptData.OBJECTEND:
            raise ScriptData.ObjectEnd

        elif typ == ScriptData.STRICTARRAY:
            length = self.read_u32()
            rval = []

            for i in range(length):
                val = self.read_script_value()
                rval.append(val)

            return rval

        elif typ == ScriptData.DATE:
            date = self.read_double()
            offset = self.read_s16()

            return ScriptData.Date(date, offset)

        elif typ == ScriptData.LONGSTRING:
            return self.read_script_string(True)

        raise IOError("Unhandled script data type: %d" % typ)

    def read_script_number(self):
        return self.read_double()

    def read_script_boolean(self):
        val = self.read_u8()

        return bool(val)

    def read_script_string(self, full=False):
        if full:
            length = self.read_u32()
        else:
            length = self.read_u16()

        val = self.read(length)

        return str(val, "utf8")

    def read_script_object(self, container):
        while True:
            try:
                key = self.read_script_string()
                value = self.read_script_value()
            except IOError:
                break

            if len(key) == 0:
                break

            container[key] = value

        self._objects.append(container)

        return container

    def write_script_value(self, val):
        if isinstance(val, bool):
            self.write_u8(ScriptData.BOOLEAN)
            self.write_script_boolean(val)

        elif isinstance(val, int) or isinstance(val, float):
            self.write_u8(ScriptData.NUMBER)
            self.write_script_number(val)

        elif isstring(val):
            if len(val) > 65535:
                self.write_u8(ScriptData.LONGSTRING)
            else:
                self.write_u8(ScriptData.STRING)

            self.write_script_string(val)

        elif isinstance(val, list):
            self.write_u8(ScriptData.STRICTARRAY)
            self.write_u32(len(val))

            for value in val:
                self.write_script_value(value)

        elif isinstance(val, ScriptData.Object):
            self.write_u8(ScriptData.OBJECT)
            self.write_script_object(val)

        elif isinstance(val, ScriptData.ECMAArray):
            self.write_u8(ScriptData.ECMAARRAY)
            self.write_u32(len(val))
            self.write_script_object(val)

        elif isinstance(val, ScriptData.Date):
            self.write_u8(ScriptData.DATE)
            self.write_double(val.timestamp)
            self.write_s16(val.offset)

        else:
            raise IOError("Cannot convert {0} to ScriptData value").format(type(val).__name__)

    def write_script_number(self, val):
        self.write_double(float(val))

    def write_script_boolean(self, val):
        self.write_u8(int(val))

    def write_script_string(self, val):
        length = len(val)

        if length > 65535:
            self.write_u32(length)
        else:
            self.write_u16(length)

        self.write(bytes(val, "utf8"))

    def write_script_object(self, val):
        for key, value in val.items():
            self.write_script_string(key)
            self.write_script_value(value)

        # Empty string + marker ends object
        self.write_script_string("")
        self.write_u8(ScriptData.OBJECTEND)

    @classmethod
    def script_value_size(cls, val):
        size = 1

        if isinstance(val, bool):
            size += 1
        elif isinstance(val, int) or isinstance(val, float):
            size += 8
        elif isstring(val):
            size += cls.script_string_size(val)

        elif isinstance(val, list):
            size += 4

            for value in val:
                size += cls.script_value_size(value)

        elif isinstance(val, ScriptData.Object):
            size += cls.script_object_size(val)

        elif isinstance(val, ScriptData.ECMAArray):
            size += 4
            size += cls.script_object_size(val)

        elif isinstance(val, ScriptData.Date):
            size += 10

        return size

    @classmethod
    def script_string_size(cls, val):
        size = len(val)

        if size > 65535:
            size += 4
        else:
            size += 2

        return size

    @classmethod
    def script_object_size(cls, val):
        size = 3

        for key, value in val.items():
            size += cls.script_string_size(key)
            size += cls.script_value_size(value)

        return size

class TagData(Packet):
    @property
    def size(self):
        if isinstance(self.data, Packet):
            return self.data.size
        else:
            return len(self.data)

__all__ = ["Packet", "PacketIO", "TagData", "ScriptData"]
<commit_msg>Ignore errors on broken UTF-8 strings.<commit_after>#!/usr/bin/env python

import struct

from .compat import *
from .util import *

class ScriptData:
    NUMBER, BOOLEAN, STRING, OBJECT, RESERVED, NULL, \
    UNDEFINED, REFERENCE, ECMAARRAY, OBJECTEND, STRICTARRAY, \
    DATE, LONGSTRING = range(13)

    class Object(OrderedDict):
        pass

    class ECMAArray(OrderedDict):
        pass

    class ObjectEnd(IOError):
        pass

    class Date(object):
        def __init__(self, timestamp, offset):
            self.timestamp = timestamp
            self.offset = offset

class Packet(object):
    @classmethod
    def _deserialize(cls):
        raise NotImplementedError

    @classmethod
    def deserialize(cls, fd=None, io=None):
        if not io:
            if not fd:
                raise IOError("Missing fd parameter")

            io = PacketIO(fd)

        return cls._deserialize(io)

    def _serialize(self):
        raise NotImplementedError

    def serialize(self, packet=None):
        if not packet:
            packet = PacketIO()

        self._serialize(packet)

        return packet.getvalue()

    def __bytes__(self):
        return self.serialize()

#    __str__ = __bytes__

class PacketIO(object):
    def __init__(self, fd=None):
        if fd:
            self.fd = fd
        else:
            self.io = BytesIO()

        self.data_left = None
        self._objects = []

    def getvalue(self):
        return self.io.getvalue()

    def read(self, size=None):
        if self.data_left is not None:
            if size is None:
                size = self.data_left

            if size > self.data_left:
                raise IOError

            self.data_left -= size

        return self.fd.read(size)

    @property
    def written(self):
        return len(self.getvalue())

    def write(self, data):
        return self.io.write(data)

    # Primitives

    def write_u8(self, num):
        return self.write(struct.pack("B", int(num)))

    def write_u16(self, num):
        return self.write(struct.pack(">H", int(num)))

    def write_s16(self, num):
        return self.write(struct.pack(">h", int(num)))

    def write_u24(self, num):
        ret = struct.pack(">I", int(num))
        return self.write(ret[1:])

    def write_s24(self, num):
        ret = struct.pack(">i", int(num))
        return self.write(ret[1:])

    def write_s32(self, num):
        return self.write(struct.pack(">i", int(num)))

    def write_u32(self, num):
        return self.write(struct.pack(">I", int(num)))

    def write_s32e(self, num):
        ret = struct.pack(">i", int(num))
        return self.write(ret[1:] + byte(ret[0]))

    def write_u64(self, num):
        return self.write(struct.pack(">Q", int(num)))

    def write_s8_8(self, num):
        num = float(num) * float(2**8)
        return self.write_s16(int(num))

    def write_s16_16(self, num):
        num = float(num) * float(2**16)
        return self.write_s32(int(num))

    def write_u3264(self, version, num):
        if version == 1:
            self.write_u64(num)
        else:
            self.write_u32(num)

    def write_double(self, num):
        return self.write(struct.pack(">d", num))

    def write_string(self, string):
        string = bytes(string, "utf8")
        return self.write(string + b"\x00")

    def write_padded(self, value, length, padding=b" "):
        for i in range(length):
            try:
                v = value[i]
                self.write(bytes(v, "ascii"))
            except IndexError:
                self.write(padding)

    def read_u8(self):
        try:
            ret = struct.unpack("B", self.read(1))[0]
        except struct.error:
            raise IOError

        return ret

    def read_u16(self):
        try:
            ret = struct.unpack(">H", self.read(2))[0]
        except struct.error:
            raise IOError

        return ret

    def read_s16(self):
        try:
            ret = struct.unpack(">h", self.read(2))[0]
        except struct.error:
            raise IOError

        return ret

    def read_s8_8(self):
        return float(self.read_s16()) / float(2**8)

    def read_s16_16(self):
        return float(self.read_s32()) / float(2**16)

    def read_s24(self):
        try:
            high, low = struct.unpack(">Bh", self.read(3))
        except struct.error:
            raise IOError

        ret = (high << 16) + low

        return ret

    def read_u24(self):
        try:
            high, low = struct.unpack(">BH", self.read(3))
        except struct.error:
            raise IOError

        ret = (high << 16) + low

        return ret

    def read_s32(self):
        try:
            ret = struct.unpack(">i", self.read(4))[0]
        except struct.error:
            raise IOError

        return ret

    def read_u32(self):
        try:
            ret = struct.unpack(">I", self.read(4))[0]
        except struct.error:
            raise IOError

        return ret

    def read_s32e(self):
        low_high = self.read(4)

        if len(low_high) > 4:
            raise IOError

        combined = byte(low_high[3]) + low_high[:3]

        try:
            ret = struct.unpack(">i", combined)[0]
        except struct.error:
            raise IOError

        return ret

    def read_u3264(self, version):
        if version == 1:
            return self.read_u64()
        else:
            return self.read_u32()

    def read_u64(self):
        try:
            ret = struct.unpack(">Q", self.read(8))[0]
        except struct.error:
            raise IOError

        return ret

    def read_double(self):
        try:
            ret = struct.unpack(">d", self.read(8))[0]
        except struct.error:
            raise IOError

        return ret

    def read_string(self):
        ret = b""

        while True:
            try:
                ch = self.read(1)
            except IOError:
                break

            if ord(ch) == 0:
                break

            ret += ch

        return str(ret, "utf8")

    def read_padded(self, length):
        return str(self.read(length), "ascii").rstrip()

    # ScriptData values

    def read_script_value(self):
        typ = self.read_u8()

        if typ == ScriptData.NUMBER:
            return self.read_script_number()

        elif typ == ScriptData.BOOLEAN:
            return self.read_script_boolean()

        elif typ == ScriptData.STRING:
            return self.read_script_string()

        elif typ == ScriptData.OBJECT:
            return self.read_script_object(ScriptData.Object())

        elif typ == ScriptData.NULL or typ == ScriptData.UNDEFINED:
            return None

        elif typ == ScriptData.REFERENCE:
            ref = self.read_u16()

            return self._objects[ref]

        elif typ == ScriptData.ECMAARRAY:
            container = ScriptData.ECMAArray()
            container.length = self.read_u32()

            return self.read_script_object(container)

        elif typ == ScriptData.OBJECTEND:
            raise ScriptData.ObjectEnd

        elif typ == ScriptData.STRICTARRAY:
            length = self.read_u32()
            rval = []

            for i in range(length):
                val = self.read_script_value()
                rval.append(val)

            return rval

        elif typ == ScriptData.DATE:
            date = self.read_double()
            offset = self.read_s16()

            return ScriptData.Date(date, offset)

        elif typ == ScriptData.LONGSTRING:
            return self.read_script_string(True)

        raise IOError("Unhandled script data type: %d" % typ)

    def read_script_number(self):
        return self.read_double()

    def read_script_boolean(self):
        val = self.read_u8()

        return bool(val)

    def read_script_string(self, full=False):
        if full:
            length = self.read_u32()
        else:
            length = self.read_u16()

        val = self.read(length)

        return val.decode("utf8", "ignore")

    def read_script_object(self, container):
        while True:
            try:
                key = self.read_script_string()
                value = self.read_script_value()
            except IOError:
                break

            if len(key) == 0:
                break

            container[key] = value

        self._objects.append(container)

        return container

    def write_script_value(self, val):
        if isinstance(val, bool):
            self.write_u8(ScriptData.BOOLEAN)
            self.write_script_boolean(val)

        elif isinstance(val, int) or isinstance(val, float):
            self.write_u8(ScriptData.NUMBER)
            self.write_script_number(val)

        elif isstring(val):
            if len(val) > 65535:
                self.write_u8(ScriptData.LONGSTRING)
            else:
                self.write_u8(ScriptData.STRING)

            self.write_script_string(val)

        elif isinstance(val, list):
            self.write_u8(ScriptData.STRICTARRAY)
            self.write_u32(len(val))

            for value in val:
                self.write_script_value(value)

        elif isinstance(val, ScriptData.Object):
            self.write_u8(ScriptData.OBJECT)
            self.write_script_object(val)

        elif isinstance(val, ScriptData.ECMAArray):
            self.write_u8(ScriptData.ECMAARRAY)
            self.write_u32(len(val))
            self.write_script_object(val)

        elif isinstance(val, ScriptData.Date):
            self.write_u8(ScriptData.DATE)
            self.write_double(val.timestamp)
            self.write_s16(val.offset)

        else:
            raise IOError("Cannot convert {0} to ScriptData value").format(type(val).__name__)

    def write_script_number(self, val):
        self.write_double(float(val))

    def write_script_boolean(self, val):
        self.write_u8(int(val))

    def write_script_string(self, val):
        length = len(val)

        if length > 65535:
            self.write_u32(length)
        else:
            self.write_u16(length)

        self.write(bytes(val, "utf8"))

    def write_script_object(self, val):
        for key, value in val.items():
            self.write_script_string(key)
            self.write_script_value(value)

        # Empty string + marker ends object
        self.write_script_string("")
        self.write_u8(ScriptData.OBJECTEND)

    @classmethod
    def script_value_size(cls, val):
        size = 1

        if isinstance(val, bool):
            size += 1
        elif isinstance(val, int) or isinstance(val, float):
            size += 8
        elif isstring(val):
            size += cls.script_string_size(val)

        elif isinstance(val, list):
            size += 4

            for value in val:
                size += cls.script_value_size(value)

        elif isinstance(val, ScriptData.Object):
            size += cls.script_object_size(val)

        elif isinstance(val, ScriptData.ECMAArray):
            size += 4
            size += cls.script_object_size(val)

        elif isinstance(val, ScriptData.Date):
            size += 10

        return size

    @classmethod
    def script_string_size(cls, val):
        size = len(val)

        if size > 65535:
            size += 4
        else:
            size += 2

        return size

    @classmethod
    def script_object_size(cls, val):
        size = 3

        for key, value in val.items():
            size += cls.script_string_size(key)
            size += cls.script_value_size(value)

        return size

class TagData(Packet):
    @property
    def size(self):
        if isinstance(self.data, Packet):
            return self.data.size
        else:
            return len(self.data)

__all__ = ["Packet", "PacketIO", "TagData", "ScriptData"]
<|endoftext|>


<commit_before>import arcpyext

try:
    from setuptools import setup
except ImportError:
    from distutils.core import setup

packages = [
    "arcpyext",
    "arcpyext.data",
    "arcpyext.exceptions",
    "arcpyext.mapping"
]

setup(
    name = "arcpyext",
    version = arcpyext.__version__,
    packages = packages,

    #misc files to include
    package_data = {
        "": ["LICENSE"]
    },

    #PyPI MetaData
    author = arcpyext.__author__,
    description = "Extended functionality for Esri's ArcPy site-package",
    license = "BSD 3-Clause",
    keywords = "arcgis esri arcpy",
    url = "https://github.com/DavidWhittingham/agstools",
    classifiers=(
        "Development Status :: 4 - Beta",
        "Intended Audience :: Developers",
        "Natural Language :: English",
        "License :: OSI Approved :: BSD License",
        "Programming Language :: Python :: 2.7"
    ),

    zip_safe = True
)<commit_msg>Fixed URL in PyPI metadata<commit_after>import arcpyext

try:
    from setuptools import setup
except ImportError:
    from distutils.core import setup

packages = [
    "arcpyext",
    "arcpyext.data",
    "arcpyext.exceptions",
    "arcpyext.mapping"
]

setup(
    name = "arcpyext",
    version = arcpyext.__version__,
    packages = packages,

    #misc files to include
    package_data = {
        "": ["LICENSE"]
    },

    #PyPI MetaData
    author = arcpyext.__author__,
    description = "Extended functionality for Esri's ArcPy site-package",
    license = "BSD 3-Clause",
    keywords = "arcgis esri arcpy",
    url = "https://github.com/DavidWhittingham/arcpyext",
    classifiers=(
        "Development Status :: 4 - Beta",
        "Intended Audience :: Developers",
        "Natural Language :: English",
        "License :: OSI Approved :: BSD License",
        "Programming Language :: Python :: 2.7"
    ),

    zip_safe = True
)<|endoftext|>


<commit_before>__version__='0.1'<commit_msg>close #18<commit_after>__version__='0.2'<|endoftext|>


<commit_before>#!/usr/bin/pythonw
# -*- coding: utf-8 -*-

from appscript import app, k, its, CommandError
from AppKit import NSApplication, NSApplicationDefined, NSBeep, NSSystemDefined, NSURL, NSWorkspace
from Foundation import NSDistributedNotificationCenter, NSSearchPathForDirectoriesInDomains, NSCachesDirectory, NSUserDomainMask
from PyObjCTools import AppHelper
from Carbon.CarbonEvt import RegisterEventHotKey, GetApplicationEventTarget
from Carbon.Events import cmdKey, shiftKey, controlKey
import httplib2
import os
import struct
import scrape
import HotKey

GROWL_APP_NAME = 'StreamVision'
NOTIFICATION_TRACK_INFO = 'iTunes Track Info'
NOTIFICATIONS_ALL = [NOTIFICATION_TRACK_INFO]

kEventHotKeyPressedSubtype = 6
kEventHotKeyReleasedSubtype = 9

kHIDUsage_Csmr_ScanNextTrack = 0xB5
kHIDUsage_Csmr_ScanPreviousTrack = 0xB6
kHIDUsage_Csmr_PlayOrPause = 0xCD

def growlRegister():
    global growl
    growl = app(id='com.Growl.GrowlHelperApp')

    growl.register(
        as_application=GROWL_APP_NAME,
        all_notifications=NOTIFICATIONS_ALL,
        default_notifications=NOTIFICATIONS_ALL,
        icon_of_application='iTunes.app')
        # if we leave off the .app, we can get Classic iTunes's icon

def growlNotify(title, description, **kw):
    try:
        growl.notify(
            with_name=NOTIFICATION_TRACK_INFO,
            title=title,
            description=description,
            application_name=GROWL_APP_NAME,
            **kw)
    except CommandError:
        growlRegister()
        growlNotify(title, description, **kw)

def radioParadiseURL():
    # XXX better to use http://www2.radioparadise.com/playlist.xml ?
    session = scrape.Session()
    session.go('http://www2.radioparadise.com/nowplay_b.php')
    return session.region.firsttag('a')['href']

def cleanStreamTitle(title):
    if title == k.missing_value:
        return ''
    title = title.split(' [')[0] # XXX move to description
    try: # incorrectly encoded?
        title = title.encode('iso-8859-1').decode('utf-8')
    except (UnicodeDecodeError, UnicodeEncodeError):
        pass
    title = title.replace('`', u'’')
    return title

def cleanStreamTrackName(name):
    name = name.split('. ')[0]
    name = name.split(': ')[0]
    name = name.split(' - ')
    if len(name) > 1:
        name = ' - '.join(name[:-1])
    else:
        name = name[0]
    return name

def iTunesApp(): return app(id='com.apple.iTunes')
def XTensionApp(): return app(creator='SHEx')
def AmuaApp(): return app('Amua.app')

HAVE_XTENSION = False
try:
    XTensionApp()
    HAVE_XTENSION = True
except:
    pass

HAVE_AMUA = False
try:
    AmuaApp()
    HAVE_AMUA = True
except:
    pass

needsStereoPowerOn = HAVE_XTENSION

def mayUseStereo():
    if not HAVE_XTENSION:
        return False
    systemEvents = app(id='com.apple.systemEvents')
    iTunesWindow = systemEvents.application_processes[u'iTunes'].windows[u'iTunes']
    try:
        remote_speakers = iTunesWindow.buttons[its.attributes['AXDescription'].value.endswith(u'remote speakers')].title()
    except CommandError:
        # with iTunes Store visible, the query fails with errAENoSuchObject
        for button in iTunesWindow.buttons():
            try:
                if button.attributes['AXDescription'].value().endswith('remote speakers'):
                    remote_speakers = [button.title()]
                    break
            except CommandError:
                pass
    return (remote_speakers and remote_speakers[0] != k.missing_value)

def turnStereoOn():
    global needsStereoPowerOn
    if not mayUseStereo():
        if HAVE_XTENSION and XTensionApp().status('Stereo'):
            XTensionApp().turnoff('Stereo')
        return
    if not XTensionApp().status('Stereo'):
        XTensionApp().turnon('Stereo')
    needsStereoPowerOn = False

def turnStereoOff():
    global needsStereoPowerOn
    if not mayUseStereo():
        return
    if not needsStereoPowerOn and XTensionApp().status('Stereo'):
        XTensionApp().turnoff('Stereo')
    needsStereoPowerOn = True

def amuaPlaying():
    if not HAVE_AMUA:
        return False
    return AmuaApp().is_playing()

class OneFileCache(object):
    __slots__ = ('key', 'cache')

    def __init__(self, cache):
        if not os.path.exists(cache):
            os.makedirs(cache)
        self.cache = os.path.join(cache, 'file')
        self.key = None

    def get(self, key):
        if key == self.key:
            return file(self.cache, 'r').read()

    def set(self, key, value):
        self.key = key
        file(self.cache, 'w').write(value)

    def delete(self, key):
        if key == self.key:
            self.key = None
            os.remove(cache)

class StreamVision(NSApplication):

    hotKeyActions = {}
    hotKeys = []

    def displayTrackInfo(self, playerInfo=None):
        iTunes = iTunesApp()

        try:
            trackClass = iTunes.current_track.class_()
        except CommandError:
            trackClass = k.property

        trackName = ''
        if trackClass != k.property:
            trackName = iTunes.current_track.name()

        if iTunes.player_state() != k.playing:
            growlNotify('iTunes is not playing.', trackName)
            turnStereoOff()
            return
        turnStereoOn()
        if trackClass == k.URL_track:
            if amuaPlaying():
                if playerInfo is None: # Amua displays it itself
                    AmuaApp().display_song_information()
                return
            url = iTunes.current_stream_URL()
            kw = {}
            if url != k.missing_value and url.endswith('.jpg'):
                response, content = self.http.request(url)
                if response['content-type'].startswith('image/'):
                    file(self.imagePath, 'w').write(content)
                    kw['image_from_location'] = self.imagePath
            growlNotify(cleanStreamTitle(iTunes.current_stream_title()),
                        cleanStreamTrackName(trackName), **kw)
            return
        if trackClass == k.property:
            growlNotify('iTunes is playing.', '')
            return
        kw = {}
        # XXX iTunes doesn't let you get artwork for shared tracks
        if trackClass != k.shared_track:
            artwork = iTunes.current_track.artworks()
            if artwork:
                try:
                    kw['pictImage'] = artwork[0].data_()
                except CommandError:
                    pass
        growlNotify(trackName + '  ' +
                    u'★' * (iTunes.current_track.rating() / 20),
                    iTunes.current_track.album() + '\n' +
                    iTunes.current_track.artist(),
                    **kw)

    def goToSite(self):
        iTunes = iTunesApp()
        if iTunes.player_state() == k.playing:
            if amuaPlaying():
                AmuaApp().display_album_details()
                return
            url = iTunes.current_stream_URL()
            if url != k.missing_value:
                if 'radioparadise.com' in url and 'review' not in url:
                    url = radioParadiseURL()
                NSWorkspace.sharedWorkspace().openURL_(NSURL.URLWithString_(url))
                return
        NSBeep()

    def registerHotKey(self, func, keyCode, mods=0):
        hotKeyRef = RegisterEventHotKey(keyCode, mods, (0, 0),
                                        GetApplicationEventTarget(), 0)
        self.hotKeys.append(hotKeyRef)
        self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)] = func
        return hotKeyRef

    def unregisterHotKey(self, hotKeyRef):
        self.hotKeys.remove(hotKeyRef)
        del self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)]
        hotKeyRef.UnregisterEventHotKey()

    def incrementRatingBy(self, increment):
        iTunes = iTunesApp()
        if amuaPlaying():
            if increment < 0:
                AmuaApp().ban_song()
                growlNotify('Banned song.', '', icon_of_application='Amua.app')
            else:
                AmuaApp().love_song()
                growlNotify('Loved song.', '', icon_of_application='Amua.app')
            return
        rating = iTunes.current_track.rating()
        rating += increment
        if rating < 0:
            rating = 0
            NSBeep()
        elif rating > 100:
            rating = 100
            NSBeep()
        iTunes.current_track.rating.set(rating)

    def playPause(self, useStereo=True):
        global needsStereoPowerOn

        iTunes = iTunesApp()
        was_playing = (iTunes.player_state() == k.playing)
        if not useStereo:
            needsStereoPowerOn = False
        if was_playing and amuaPlaying():
            AmuaApp().stop()
        else:
            iTunes.playpause()
        if not was_playing and iTunes.player_state() == k.stopped:
            # most likely, we're focused on the iPod, so playing does nothing
            iTunes.browser_windows[1].view.set(iTunes.user_playlists[its.name=='Stations'][1]())
            iTunes.play()
        if not useStereo:
            return
        if iTunes.player_state() == k.playing:
            turnStereoOn()
        else:
            turnStereoOff()

    def playPauseFront(self):
        systemEvents = app(id='com.apple.systemEvents')
        frontName = systemEvents.processes[its.frontmost == True][1].name()
	if frontName == 'RealPlayer':
	    realPlayer = app(id='com.RealNetworks.RealPlayer')
	    if len(realPlayer.players()) > 0:
		if realPlayer.players[1].state() == k.playing:
		    realPlayer.pause()
		else:
		    realPlayer.play()
		return
	elif frontName == 'VLC':
	    app(id='org.videolan.vlc').play() # equivalent to playpause
	else:
	    self.playPause(useStereo=False)

    def nextTrack(self):
        if amuaPlaying():
            AmuaApp().skip_song()
            return
        iTunesApp().next_track()

    def registerZoomWindowHotKey(self):
        self.zoomWindowHotKey = self.registerHotKey(self.zoomWindow, 42, cmdKey | controlKey) # cmd-ctrl-\

    def unregisterZoomWindowHotKey(self):
        self.unregisterHotKey(self.zoomWindowHotKey)
        self.zoomWindowHotKey = None

    def zoomWindow(self):
        # XXX detect if "enable access for assistive devices" needs to be enabled
        systemEvents = app(id='com.apple.systemEvents')
        frontName = systemEvents.processes[its.frontmost == True][1].name()
        if frontName == 'iTunes':
            systemEvents.processes['iTunes'].menu_bars[1]. \
                menu_bar_items['Window'].menus.menu_items['Zoom'].click()
            return
        elif frontName in ('X11', 'XQuartz', 'Emacs'): # preserve C-M-\
            self.unregisterZoomWindowHotKey()
            systemEvents.key_code(42, using=[k.command_down, k.control_down])
            self.registerZoomWindowHotKey()
            return
        frontPID = systemEvents.processes[its.frontmost == True][1].unix_id()
        try:
            zoomed = app(pid=frontPID).windows[1].zoomed
            zoomed.set(not zoomed())
        except (CommandError, RuntimeError):
            systemEvents.processes[frontName].windows \
                [its.subrole == 'AXStandardWindow'].windows[1]. \
                buttons[its.subrole == 'AXZoomButton'].buttons[1].click()

    def finishLaunching(self):
        super(StreamVision, self).finishLaunching()

        caches = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
                                                     NSUserDomainMask, True)[0]
        cache = os.path.join(caches, 'StreamVision')
        self.http = httplib2.Http(OneFileCache(cache), 5)
        self.imagePath = os.path.join(cache, 'image')

        self.registerHotKey(self.displayTrackInfo, 100) # F8
        self.registerHotKey(self.goToSite, 100, cmdKey) # cmd-F8
        self.registerHotKey(self.playPause, 101) # F9
        self.registerHotKey(lambda: iTunesApp().previous_track(), 109) # F10
        self.registerHotKey(self.nextTrack, 103) # F11
        self.registerHotKey(lambda: self.incrementRatingBy(-20), 109, shiftKey) # shift-F10
        self.registerHotKey(lambda: self.incrementRatingBy(20), 103, shiftKey) # shift-F11
        self.registerZoomWindowHotKey()
        NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, self.displayTrackInfo, 'com.apple.iTunes.playerInfo', None)
        try:
            import HIDRemote
            HIDRemote.connect()
        except ImportError:
            print "failed to import HIDRemote (XXX fix - on Intel)"
        except OSError, e:
            print "failed to connect to remote: ", e

    def sendEvent_(self, theEvent):
        eventType = theEvent.type()
        if eventType == NSSystemDefined and \
               theEvent.subtype() == kEventHotKeyPressedSubtype:
            self.hotKeyActions[theEvent.data1()]()
        elif eventType == NSApplicationDefined:
            key = theEvent.data1()
            if key == kHIDUsage_Csmr_ScanNextTrack:
                self.nextTrack()
            elif key == kHIDUsage_Csmr_ScanPreviousTrack:
                iTunesApp().previous_track()
            elif key == kHIDUsage_Csmr_PlayOrPause:
                self.playPauseFront()
        super(StreamVision, self).sendEvent_(theEvent)

if __name__ == "__main__":
    growlRegister()
    AppHelper.runEventLoop()
    try:
        HIDRemote.disconnect()
    except:
        pass
<commit_msg>StreamVision.py: iTunes 10.1.2 uses "AirPlay" rather than "remote speakers" in button description.<commit_after>#!/usr/bin/pythonw
# -*- coding: utf-8 -*-

from appscript import app, k, its, CommandError
from AppKit import NSApplication, NSApplicationDefined, NSBeep, NSSystemDefined, NSURL, NSWorkspace
from Foundation import NSDistributedNotificationCenter, NSSearchPathForDirectoriesInDomains, NSCachesDirectory, NSUserDomainMask
from PyObjCTools import AppHelper
from Carbon.CarbonEvt import RegisterEventHotKey, GetApplicationEventTarget
from Carbon.Events import cmdKey, shiftKey, controlKey
import httplib2
import os
import struct
import scrape
import HotKey

GROWL_APP_NAME = 'StreamVision'
NOTIFICATION_TRACK_INFO = 'iTunes Track Info'
NOTIFICATIONS_ALL = [NOTIFICATION_TRACK_INFO]

kEventHotKeyPressedSubtype = 6
kEventHotKeyReleasedSubtype = 9

kHIDUsage_Csmr_ScanNextTrack = 0xB5
kHIDUsage_Csmr_ScanPreviousTrack = 0xB6
kHIDUsage_Csmr_PlayOrPause = 0xCD

def growlRegister():
    global growl
    growl = app(id='com.Growl.GrowlHelperApp')

    growl.register(
        as_application=GROWL_APP_NAME,
        all_notifications=NOTIFICATIONS_ALL,
        default_notifications=NOTIFICATIONS_ALL,
        icon_of_application='iTunes.app')
        # if we leave off the .app, we can get Classic iTunes's icon

def growlNotify(title, description, **kw):
    try:
        growl.notify(
            with_name=NOTIFICATION_TRACK_INFO,
            title=title,
            description=description,
            application_name=GROWL_APP_NAME,
            **kw)
    except CommandError:
        growlRegister()
        growlNotify(title, description, **kw)

def radioParadiseURL():
    # XXX better to use http://www2.radioparadise.com/playlist.xml ?
    session = scrape.Session()
    session.go('http://www2.radioparadise.com/nowplay_b.php')
    return session.region.firsttag('a')['href']

def cleanStreamTitle(title):
    if title == k.missing_value:
        return ''
    title = title.split(' [')[0] # XXX move to description
    try: # incorrectly encoded?
        title = title.encode('iso-8859-1').decode('utf-8')
    except (UnicodeDecodeError, UnicodeEncodeError):
        pass
    title = title.replace('`', u'’')
    return title

def cleanStreamTrackName(name):
    name = name.split('. ')[0]
    name = name.split(': ')[0]
    name = name.split(' - ')
    if len(name) > 1:
        name = ' - '.join(name[:-1])
    else:
        name = name[0]
    return name

def iTunesApp(): return app(id='com.apple.iTunes')
def XTensionApp(): return app(creator='SHEx')
def AmuaApp(): return app('Amua.app')

HAVE_XTENSION = False
try:
    XTensionApp()
    HAVE_XTENSION = True
except:
    pass

HAVE_AMUA = False
try:
    AmuaApp()
    HAVE_AMUA = True
except:
    pass

needsStereoPowerOn = HAVE_XTENSION

def mayUseStereo():
    if not HAVE_XTENSION:
        return False
    systemEvents = app(id='com.apple.systemEvents')
    iTunesWindow = systemEvents.application_processes[u'iTunes'].windows[u'iTunes']
    try:
        remote_speakers = iTunesWindow.buttons[its.attributes['AXDescription'].value.endswith(u'AirPlay')].title()
    except CommandError:
        # with iTunes Store visible, the query fails with errAENoSuchObject
        for button in iTunesWindow.buttons():
            try:
                if button.attributes['AXDescription'].value().endswith('remote speakers'):
                    remote_speakers = [button.title()]
                    break
            except CommandError:
                pass
    return (remote_speakers and remote_speakers[0] != k.missing_value)

def turnStereoOn():
    global needsStereoPowerOn
    if not mayUseStereo():
        if HAVE_XTENSION and XTensionApp().status('Stereo'):
            XTensionApp().turnoff('Stereo')
        return
    if not XTensionApp().status('Stereo'):
        XTensionApp().turnon('Stereo')
    needsStereoPowerOn = False

def turnStereoOff():
    global needsStereoPowerOn
    if not mayUseStereo():
        return
    if not needsStereoPowerOn and XTensionApp().status('Stereo'):
        XTensionApp().turnoff('Stereo')
    needsStereoPowerOn = True

def amuaPlaying():
    if not HAVE_AMUA:
        return False
    return AmuaApp().is_playing()

class OneFileCache(object):
    __slots__ = ('key', 'cache')

    def __init__(self, cache):
        if not os.path.exists(cache):
            os.makedirs(cache)
        self.cache = os.path.join(cache, 'file')
        self.key = None

    def get(self, key):
        if key == self.key:
            return file(self.cache, 'r').read()

    def set(self, key, value):
        self.key = key
        file(self.cache, 'w').write(value)

    def delete(self, key):
        if key == self.key:
            self.key = None
            os.remove(cache)

class StreamVision(NSApplication):

    hotKeyActions = {}
    hotKeys = []

    def displayTrackInfo(self, playerInfo=None):
        iTunes = iTunesApp()

        try:
            trackClass = iTunes.current_track.class_()
        except CommandError:
            trackClass = k.property

        trackName = ''
        if trackClass != k.property:
            trackName = iTunes.current_track.name()

        if iTunes.player_state() != k.playing:
            growlNotify('iTunes is not playing.', trackName)
            turnStereoOff()
            return
        turnStereoOn()
        if trackClass == k.URL_track:
            if amuaPlaying():
                if playerInfo is None: # Amua displays it itself
                    AmuaApp().display_song_information()
                return
            url = iTunes.current_stream_URL()
            kw = {}
            if url != k.missing_value and url.endswith('.jpg'):
                response, content = self.http.request(url)
                if response['content-type'].startswith('image/'):
                    file(self.imagePath, 'w').write(content)
                    kw['image_from_location'] = self.imagePath
            growlNotify(cleanStreamTitle(iTunes.current_stream_title()),
                        cleanStreamTrackName(trackName), **kw)
            return
        if trackClass == k.property:
            growlNotify('iTunes is playing.', '')
            return
        kw = {}
        # XXX iTunes doesn't let you get artwork for shared tracks
        if trackClass != k.shared_track:
            artwork = iTunes.current_track.artworks()
            if artwork:
                try:
                    kw['pictImage'] = artwork[0].data_()
                except CommandError:
                    pass
        growlNotify(trackName + '  ' +
                    u'★' * (iTunes.current_track.rating() / 20),
                    iTunes.current_track.album() + '\n' +
                    iTunes.current_track.artist(),
                    **kw)

    def goToSite(self):
        iTunes = iTunesApp()
        if iTunes.player_state() == k.playing:
            if amuaPlaying():
                AmuaApp().display_album_details()
                return
            url = iTunes.current_stream_URL()
            if url != k.missing_value:
                if 'radioparadise.com' in url and 'review' not in url:
                    url = radioParadiseURL()
                NSWorkspace.sharedWorkspace().openURL_(NSURL.URLWithString_(url))
                return
        NSBeep()

    def registerHotKey(self, func, keyCode, mods=0):
        hotKeyRef = RegisterEventHotKey(keyCode, mods, (0, 0),
                                        GetApplicationEventTarget(), 0)
        self.hotKeys.append(hotKeyRef)
        self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)] = func
        return hotKeyRef

    def unregisterHotKey(self, hotKeyRef):
        self.hotKeys.remove(hotKeyRef)
        del self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)]
        hotKeyRef.UnregisterEventHotKey()

    def incrementRatingBy(self, increment):
        iTunes = iTunesApp()
        if amuaPlaying():
            if increment < 0:
                AmuaApp().ban_song()
                growlNotify('Banned song.', '', icon_of_application='Amua.app')
            else:
                AmuaApp().love_song()
                growlNotify('Loved song.', '', icon_of_application='Amua.app')
            return
        rating = iTunes.current_track.rating()
        rating += increment
        if rating < 0:
            rating = 0
            NSBeep()
        elif rating > 100:
            rating = 100
            NSBeep()
        iTunes.current_track.rating.set(rating)

    def playPause(self, useStereo=True):
        global needsStereoPowerOn

        iTunes = iTunesApp()
        was_playing = (iTunes.player_state() == k.playing)
        if not useStereo:
            needsStereoPowerOn = False
        if was_playing and amuaPlaying():
            AmuaApp().stop()
        else:
            iTunes.playpause()
        if not was_playing and iTunes.player_state() == k.stopped:
            # most likely, we're focused on the iPod, so playing does nothing
            iTunes.browser_windows[1].view.set(iTunes.user_playlists[its.name=='Stations'][1]())
            iTunes.play()
        if not useStereo:
            return
        if iTunes.player_state() == k.playing:
            turnStereoOn()
        else:
            turnStereoOff()

    def playPauseFront(self):
        systemEvents = app(id='com.apple.systemEvents')
        frontName = systemEvents.processes[its.frontmost == True][1].name()
	if frontName == 'RealPlayer':
	    realPlayer = app(id='com.RealNetworks.RealPlayer')
	    if len(realPlayer.players()) > 0:
		if realPlayer.players[1].state() == k.playing:
		    realPlayer.pause()
		else:
		    realPlayer.play()
		return
	elif frontName == 'VLC':
	    app(id='org.videolan.vlc').play() # equivalent to playpause
	else:
	    self.playPause(useStereo=False)

    def nextTrack(self):
        if amuaPlaying():
            AmuaApp().skip_song()
            return
        iTunesApp().next_track()

    def registerZoomWindowHotKey(self):
        self.zoomWindowHotKey = self.registerHotKey(self.zoomWindow, 42, cmdKey | controlKey) # cmd-ctrl-\

    def unregisterZoomWindowHotKey(self):
        self.unregisterHotKey(self.zoomWindowHotKey)
        self.zoomWindowHotKey = None

    def zoomWindow(self):
        # XXX detect if "enable access for assistive devices" needs to be enabled
        systemEvents = app(id='com.apple.systemEvents')
        frontName = systemEvents.processes[its.frontmost == True][1].name()
        if frontName == 'iTunes':
            systemEvents.processes['iTunes'].menu_bars[1]. \
                menu_bar_items['Window'].menus.menu_items['Zoom'].click()
            return
        elif frontName in ('X11', 'XQuartz', 'Emacs'): # preserve C-M-\
            self.unregisterZoomWindowHotKey()
            systemEvents.key_code(42, using=[k.command_down, k.control_down])
            self.registerZoomWindowHotKey()
            return
        frontPID = systemEvents.processes[its.frontmost == True][1].unix_id()
        try:
            zoomed = app(pid=frontPID).windows[1].zoomed
            zoomed.set(not zoomed())
        except (CommandError, RuntimeError):
            systemEvents.processes[frontName].windows \
                [its.subrole == 'AXStandardWindow'].windows[1]. \
                buttons[its.subrole == 'AXZoomButton'].buttons[1].click()

    def finishLaunching(self):
        super(StreamVision, self).finishLaunching()

        caches = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
                                                     NSUserDomainMask, True)[0]
        cache = os.path.join(caches, 'StreamVision')
        self.http = httplib2.Http(OneFileCache(cache), 5)
        self.imagePath = os.path.join(cache, 'image')

        self.registerHotKey(self.displayTrackInfo, 100) # F8
        self.registerHotKey(self.goToSite, 100, cmdKey) # cmd-F8
        self.registerHotKey(self.playPause, 101) # F9
        self.registerHotKey(lambda: iTunesApp().previous_track(), 109) # F10
        self.registerHotKey(self.nextTrack, 103) # F11
        self.registerHotKey(lambda: self.incrementRatingBy(-20), 109, shiftKey) # shift-F10
        self.registerHotKey(lambda: self.incrementRatingBy(20), 103, shiftKey) # shift-F11
        self.registerZoomWindowHotKey()
        NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, self.displayTrackInfo, 'com.apple.iTunes.playerInfo', None)
        try:
            import HIDRemote
            HIDRemote.connect()
        except ImportError:
            print "failed to import HIDRemote (XXX fix - on Intel)"
        except OSError, e:
            print "failed to connect to remote: ", e

    def sendEvent_(self, theEvent):
        eventType = theEvent.type()
        if eventType == NSSystemDefined and \
               theEvent.subtype() == kEventHotKeyPressedSubtype:
            self.hotKeyActions[theEvent.data1()]()
        elif eventType == NSApplicationDefined:
            key = theEvent.data1()
            if key == kHIDUsage_Csmr_ScanNextTrack:
                self.nextTrack()
            elif key == kHIDUsage_Csmr_ScanPreviousTrack:
                iTunesApp().previous_track()
            elif key == kHIDUsage_Csmr_PlayOrPause:
                self.playPauseFront()
        super(StreamVision, self).sendEvent_(theEvent)

if __name__ == "__main__":
    growlRegister()
    AppHelper.runEventLoop()
    try:
        HIDRemote.disconnect()
    except:
        pass
<|endoftext|>


<commit_before>#!/usr/bin/pythonw
# -*- coding: utf-8 -*-

from appscript import app, k, its, CommandError
from AppKit import NSApplication, NSApplicationDefined, NSBeep, NSSystemDefined, NSURL, NSWorkspace
from Foundation import NSDistributedNotificationCenter, NSSearchPathForDirectoriesInDomains, NSCachesDirectory, NSUserDomainMask
from PyObjCTools import AppHelper
from Carbon.CarbonEvt import RegisterEventHotKey, GetApplicationEventTarget
from Carbon.Events import cmdKey, shiftKey, controlKey
from AudioDevice import default_output_device_is_airplay, set_default_output_device_changed_callback
import httplib2
import os
import struct
import scrape
import HotKey
import tunes # <https://github.com/abarnert/itunesterms>

GROWL_APP_NAME = 'StreamVision'
NOTIFICATION_TRACK_INFO = 'iTunes Track Info'
NOTIFICATIONS_ALL = [NOTIFICATION_TRACK_INFO]

kEventHotKeyPressedSubtype = 6
kEventHotKeyReleasedSubtype = 9

kHIDUsage_Csmr_ScanNextTrack = 0xB5
kHIDUsage_Csmr_ScanPreviousTrack = 0xB6
kHIDUsage_Csmr_PlayOrPause = 0xCD

def growlRegister():
    global growl
    growl = app(id='com.Growl.GrowlHelperApp')

    growl.register(
        as_application=GROWL_APP_NAME,
        all_notifications=NOTIFICATIONS_ALL,
        default_notifications=NOTIFICATIONS_ALL,
        icon_of_application='iTunes.app')
        # if we leave off the .app, we can get Classic iTunes's icon

def growlNotify(title, description, **kw):
    try:
        if usingStereo:
            description += '\n(AirPlay)'

        growl.notify(
            with_name=NOTIFICATION_TRACK_INFO,
            title=title,
            description=description,
            application_name=GROWL_APP_NAME,
            **kw)
    except CommandError:
        growlRegister()
        growlNotify(title, description, **kw)

def radioParadiseURL():
    session = scrape.Session()
    session.go('http://radioparadise.com/jq_playlist.php')
    url = session.region.firsttag('a')['href']
    if not url.startswith('http'):
    	url = 'http://www.radioparadise.com/rp2-' + url
	return url

def cleanStreamTitle(title):
    if title == k.missing_value:
        return ''
    title = title.split(' [')[0] # XXX move to description
    try: # incorrectly encoded?
        title = title.encode('iso-8859-1').decode('utf-8')
    except (UnicodeDecodeError, UnicodeEncodeError):
        pass
    title = title.replace('`', u'’')
    return title

def cleanStreamTrackName(name):
    name = name.split('. ')[0]
    name = name.split(': ')[0]
    name = name.split(' - ')
    if len(name) > 1:
        name = ' - '.join(name[:-1])
    else:
        name = name[0]
    return name

def iTunesApp(): return app(id='com.apple.iTunes', terms=tunes)
def XTensionApp(): return app(creator='SHEx')
def AmuaApp(): return app('Amua.app')

HAVE_XTENSION = False
try:
    XTensionApp()
    HAVE_XTENSION = True
except:
    pass

HAVE_AMUA = False
try:
    AmuaApp()
    HAVE_AMUA = True
except:
    pass

needsStereoPowerOn = HAVE_XTENSION
usingStereo = False

def mayUseStereo():
    if not HAVE_XTENSION:
        return False
    systemEvents = app(id='com.apple.systemEvents')
    iTunesWindow = systemEvents.application_processes[u'iTunes'].windows[u'iTunes']
    # Can't get AirPlay status with iTunes Mini Player or window on other Space.
    try:
        remote_speakers = iTunesWindow.buttons[its.attributes['AXDescription'].value.beginswith(u'AirPlay')].title()
    except CommandError: # window on another Space?
        return usingStereo
    return remote_speakers and remote_speakers[0] not in (None, k.missing_value)

def turnStereoOnOrOff():
    global needsStereoPowerOn, usingStereo
    usingStereo = False
    if not default_output_device_is_airplay() and not mayUseStereo():
        if HAVE_XTENSION and XTensionApp().status('Stereo'):
            XTensionApp().turnoff('Stereo')
        return
    if not XTensionApp().status('Stereo'):
        XTensionApp().turnon('Stereo')
    usingStereo = True
    needsStereoPowerOn = False

def turnStereoOff():
    global needsStereoPowerOn, usingStereo
    usingStereo = False
    if default_output_device_is_airplay() or not mayUseStereo():
        return
    if not needsStereoPowerOn and XTensionApp().status('Stereo'):
        XTensionApp().turnoff('Stereo')
    needsStereoPowerOn = True

def amuaPlaying():
    if not HAVE_AMUA:
        return False
    return AmuaApp().is_playing()

class OneFileCache(object):
    __slots__ = ('key', 'cache')

    def __init__(self, cache):
        if not os.path.exists(cache):
            os.makedirs(cache)
        self.cache = os.path.join(cache, 'file')
        self.key = None

    def get(self, key):
        if key == self.key:
            return file(self.cache, 'r').read()

    def set(self, key, value):
        self.key = key
        file(self.cache, 'w').write(value)

    def delete(self, key):
        if key == self.key:
            self.key = None
            os.remove(cache)

class StreamVision(NSApplication):

    hotKeyActions = {}
    hotKeys = []

    def displayTrackInfo(self, playerInfo=None):
        iTunes = iTunesApp()

        try:
            trackClass = iTunes.current_track.class_()
        except CommandError:
            trackClass = k.property

        trackName = ''
        if trackClass != k.property:
            trackName = iTunes.current_track.name()

        try:
            playerState = iTunes.player_state()
        except CommandError:
            playerState = None # probably iTunes quit
        if playerState != k.playing:
            if playerState != None:
                growlNotify('iTunes is not playing.', trackName)
            turnStereoOff()
            return
        turnStereoOnOrOff()
        if trackClass == k.URL_track:
            if amuaPlaying():
                if playerInfo is None: # Amua displays it itself
                    AmuaApp().display_song_information()
                return
            url = iTunes.current_stream_URL()
            kw = {}
            if url != k.missing_value and url.endswith('.jpg'):
                try:
                    response, content = self.http.request(url)
                except Exception, e:
                    import sys
                    print >> sys.stderr, 'Request for album art failed:', e
                else:
                    if response['content-type'].startswith('image/'):
                        file(self.imagePath, 'w').write(content)
                        kw['image_from_location'] = self.imagePath
            growlNotify(cleanStreamTitle(iTunes.current_stream_title()),
                        cleanStreamTrackName(trackName), **kw)
            return
        if trackClass == k.property:
            growlNotify('iTunes is playing.', '')
            return
        kw = {}
        # XXX iTunes doesn't let you get artwork for shared tracks
        if trackClass != k.shared_track:
            artwork = iTunes.current_track.artworks()
            if artwork:
                try:
                    kw['pictImage'] = artwork[0].data_()
                except CommandError:
                    pass
        growlNotify(trackName + '  ' +
                    u'★' * (iTunes.current_track.rating() / 20),
                    iTunes.current_track.album() + '\n' +
                    iTunes.current_track.artist(),
                    **kw)

    def defaultOutputDeviceChanged(self):
        turnStereoOnOrOff()
        self.displayTrackInfo()

    def goToSite(self):
        iTunes = iTunesApp()
        if iTunes.player_state() == k.playing:
            if amuaPlaying():
                AmuaApp().display_album_details()
                return
            url = iTunes.current_stream_URL()
            if url != k.missing_value:
                if 'radioparadise.com' in url and 'review' not in url:
                    url = radioParadiseURL()
                NSWorkspace.sharedWorkspace().openURL_(NSURL.URLWithString_(url))
                return
        NSBeep()

    def registerHotKey(self, func, keyCode, mods=0):
        hotKeyRef = RegisterEventHotKey(keyCode, mods, (0, 0),
                                        GetApplicationEventTarget(), 0)
        self.hotKeys.append(hotKeyRef)
        self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)] = func
        return hotKeyRef

    def unregisterHotKey(self, hotKeyRef):
        self.hotKeys.remove(hotKeyRef)
        del self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)]
        hotKeyRef.UnregisterEventHotKey()

    def incrementRatingBy(self, increment):
        iTunes = iTunesApp()
        if amuaPlaying():
            if increment < 0:
                AmuaApp().ban_song()
                growlNotify('Banned song.', '', icon_of_application='Amua.app')
            else:
                AmuaApp().love_song()
                growlNotify('Loved song.', '', icon_of_application='Amua.app')
            return
        rating = iTunes.current_track.rating()
        rating += increment
        if rating < 0:
            rating = 0
            NSBeep()
        elif rating > 100:
            rating = 100
            NSBeep()
        iTunes.current_track.rating.set(rating)

    def playPause(self, useStereo=True):
        global needsStereoPowerOn

        iTunes = iTunesApp()
        was_playing = (iTunes.player_state() == k.playing)
        if not useStereo:
            needsStereoPowerOn = False
        if was_playing and amuaPlaying():
            AmuaApp().stop()
        else:
            iTunes.playpause()
        if not was_playing and iTunes.player_state() == k.stopped:
            # most likely, we're focused on the iPod, so playing does nothing
            iTunes.browser_windows[1].view.set(iTunes.user_playlists[its.name=='Stations'][1]())
            iTunes.play()
        if not useStereo:
            return
        if iTunes.player_state() == k.playing:
            turnStereoOnOrOff()
        else:
            turnStereoOff()

    def playPauseFront(self):
        systemEvents = app(id='com.apple.systemEvents')
        frontName = systemEvents.processes[its.frontmost == True][1].name()
	if frontName == 'RealPlayer':
	    realPlayer = app(id='com.RealNetworks.RealPlayer')
	    if len(realPlayer.players()) > 0:
		if realPlayer.players[1].state() == k.playing:
		    realPlayer.pause()
		else:
		    realPlayer.play()
		return
	elif frontName == 'VLC':
	    app(id='org.videolan.vlc').play() # equivalent to playpause
	else:
	    self.playPause(useStereo=False)

    def nextTrack(self):
        if amuaPlaying():
            AmuaApp().skip_song()
            return
        iTunesApp().next_track()

    def registerZoomWindowHotKey(self):
        self.zoomWindowHotKey = self.registerHotKey(self.zoomWindow, 42, cmdKey | controlKey) # cmd-ctrl-\

    def unregisterZoomWindowHotKey(self):
        self.unregisterHotKey(self.zoomWindowHotKey)
        self.zoomWindowHotKey = None

    def zoomWindow(self):
        # XXX detect if "enable access for assistive devices" needs to be enabled
        systemEvents = app(id='com.apple.systemEvents')
        frontName = systemEvents.processes[its.frontmost == True][1].name()
        if frontName == 'iTunes':
            systemEvents.processes['iTunes'].menu_bars[1]. \
                menu_bar_items['Window'].menus.menu_items['Zoom'].click()
            return
        elif frontName in ('X11', 'XQuartz', 'Emacs'): # preserve C-M-\
            self.unregisterZoomWindowHotKey()
            systemEvents.key_code(42, using=[k.command_down, k.control_down])
            self.registerZoomWindowHotKey()
            return
        frontPID = systemEvents.processes[its.frontmost == True][1].unix_id()
        try:
            zoomed = app(pid=frontPID).windows[1].zoomed
            zoomed.set(not zoomed())
        except (CommandError, RuntimeError):
            systemEvents.processes[frontName].windows \
                [its.subrole == 'AXStandardWindow'].windows[1]. \
                buttons[its.subrole == 'AXZoomButton'].buttons[1].click()

    def finishLaunching(self):
        super(StreamVision, self).finishLaunching()

        caches = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
                                                     NSUserDomainMask, True)[0]
        cache = os.path.join(caches, 'StreamVision')
        self.http = httplib2.Http(OneFileCache(cache), 5)
        self.imagePath = os.path.join(cache, 'image')

        self.registerHotKey(self.displayTrackInfo, 100) # F8
        self.registerHotKey(self.goToSite, 100, cmdKey) # cmd-F8
        self.registerHotKey(self.playPause, 101) # F9
        self.registerHotKey(lambda: iTunesApp().previous_track(), 109) # F10
        self.registerHotKey(self.nextTrack, 103) # F11
        self.registerHotKey(lambda: self.incrementRatingBy(-20), 109, shiftKey) # shift-F10
        self.registerHotKey(lambda: self.incrementRatingBy(20), 103, shiftKey) # shift-F11
        self.registerZoomWindowHotKey()
        NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, self.displayTrackInfo, 'com.apple.iTunes.playerInfo', None)
        try:
            import HIDRemote
            HIDRemote.connect()
        except ImportError:
            print "failed to import HIDRemote (XXX fix - on Intel)"
        except OSError, e:
            print "failed to connect to remote: ", e

        set_default_output_device_changed_callback(
            self.defaultOutputDeviceChanged)
        turnStereoOnOrOff()

    def sendEvent_(self, theEvent):
        eventType = theEvent.type()
        if eventType == NSSystemDefined and \
               theEvent.subtype() == kEventHotKeyPressedSubtype:
            self.hotKeyActions[theEvent.data1()]()
        elif eventType == NSApplicationDefined:
            key = theEvent.data1()
            if key == kHIDUsage_Csmr_ScanNextTrack:
                self.nextTrack()
            elif key == kHIDUsage_Csmr_ScanPreviousTrack:
                iTunesApp().previous_track()
            elif key == kHIDUsage_Csmr_PlayOrPause:
                self.playPauseFront()
        super(StreamVision, self).sendEvent_(theEvent)

if __name__ == "__main__":
    growlRegister()
    AppHelper.runEventLoop()
    try:
        HIDRemote.disconnect()
    except:
        pass
<commit_msg>StreamVision.py: Remove terminology workaround, no longer necessary for current iTunes 10 or 11.  Use new Apple Event exposed AirPlay queries in iTunes 11.0.3 if available.<commit_after>#!/usr/bin/pythonw
# -*- coding: utf-8 -*-

from appscript import app, k, its, CommandError
from AppKit import NSApplication, NSApplicationDefined, NSBeep, NSSystemDefined, NSURL, NSWorkspace
from Foundation import NSDistributedNotificationCenter, NSSearchPathForDirectoriesInDomains, NSCachesDirectory, NSUserDomainMask
from PyObjCTools import AppHelper
from Carbon.CarbonEvt import RegisterEventHotKey, GetApplicationEventTarget
from Carbon.Events import cmdKey, shiftKey, controlKey
from AudioDevice import default_output_device_is_airplay, set_default_output_device_changed_callback
import httplib2
import os
import struct
import scrape
import HotKey

GROWL_APP_NAME = 'StreamVision'
NOTIFICATION_TRACK_INFO = 'iTunes Track Info'
NOTIFICATIONS_ALL = [NOTIFICATION_TRACK_INFO]

kEventHotKeyPressedSubtype = 6
kEventHotKeyReleasedSubtype = 9

kHIDUsage_Csmr_ScanNextTrack = 0xB5
kHIDUsage_Csmr_ScanPreviousTrack = 0xB6
kHIDUsage_Csmr_PlayOrPause = 0xCD

def growlRegister():
    global growl
    growl = app(id='com.Growl.GrowlHelperApp')

    growl.register(
        as_application=GROWL_APP_NAME,
        all_notifications=NOTIFICATIONS_ALL,
        default_notifications=NOTIFICATIONS_ALL,
        icon_of_application='iTunes.app')
        # if we leave off the .app, we can get Classic iTunes's icon

def growlNotify(title, description, **kw):
    try:
        if usingStereo:
            description += '\n(AirPlay)'

        growl.notify(
            with_name=NOTIFICATION_TRACK_INFO,
            title=title,
            description=description,
            application_name=GROWL_APP_NAME,
            **kw)
    except CommandError:
        growlRegister()
        growlNotify(title, description, **kw)

def radioParadiseURL():
    session = scrape.Session()
    session.go('http://radioparadise.com/jq_playlist.php')
    url = session.region.firsttag('a')['href']
    if not url.startswith('http'):
    	url = 'http://www.radioparadise.com/rp2-' + url
	return url

def cleanStreamTitle(title):
    if title == k.missing_value:
        return ''
    title = title.split(' [')[0] # XXX move to description
    try: # incorrectly encoded?
        title = title.encode('iso-8859-1').decode('utf-8')
    except (UnicodeDecodeError, UnicodeEncodeError):
        pass
    title = title.replace('`', u'’')
    return title

def cleanStreamTrackName(name):
    name = name.split('. ')[0]
    name = name.split(': ')[0]
    name = name.split(' - ')
    if len(name) > 1:
        name = ' - '.join(name[:-1])
    else:
        name = name[0]
    return name

def iTunesApp(): return app(id='com.apple.iTunes')
def XTensionApp(): return app(creator='SHEx')
def AmuaApp(): return app('Amua.app')

HAVE_XTENSION = False
try:
    XTensionApp()
    HAVE_XTENSION = True
except:
    pass

HAVE_AMUA = False
try:
    AmuaApp()
    HAVE_AMUA = True
except:
    pass

needsStereoPowerOn = HAVE_XTENSION
usingStereo = False

def mayUseStereo():
    if not HAVE_XTENSION:
        return False
    try:
        # A bit better in iTunes 11.0.3, but can't do this via an Apple
        # Event descriptor; have to send multiple events
        iTunes = iTunesApp()
        return any(d.kind() != k.computer for d in it.current_AirPlay_devices())
    except AttributeError:
        pass

    systemEvents = app(id='com.apple.systemEvents')
    iTunesWindow = systemEvents.application_processes[u'iTunes'].windows[u'iTunes']
    # Can't get AirPlay status with iTunes Mini Player or window on other Space.
    try:
        remote_speakers = iTunesWindow.buttons[its.attributes['AXDescription'].value.beginswith(u'AirPlay')].title()
    except CommandError: # window on another Space?
        return usingStereo
    return remote_speakers and remote_speakers[0] not in (None, k.missing_value)

def turnStereoOnOrOff():
    global needsStereoPowerOn, usingStereo
    usingStereo = False
    if not default_output_device_is_airplay() and not mayUseStereo():
        if HAVE_XTENSION and XTensionApp().status('Stereo'):
            XTensionApp().turnoff('Stereo')
        return
    if not XTensionApp().status('Stereo'):
        XTensionApp().turnon('Stereo')
    usingStereo = True
    needsStereoPowerOn = False

def turnStereoOff():
    global needsStereoPowerOn, usingStereo
    usingStereo = False
    if default_output_device_is_airplay() or not mayUseStereo():
        return
    if not needsStereoPowerOn and XTensionApp().status('Stereo'):
        XTensionApp().turnoff('Stereo')
    needsStereoPowerOn = True

def amuaPlaying():
    if not HAVE_AMUA:
        return False
    return AmuaApp().is_playing()

class OneFileCache(object):
    __slots__ = ('key', 'cache')

    def __init__(self, cache):
        if not os.path.exists(cache):
            os.makedirs(cache)
        self.cache = os.path.join(cache, 'file')
        self.key = None

    def get(self, key):
        if key == self.key:
            return file(self.cache, 'r').read()

    def set(self, key, value):
        self.key = key
        file(self.cache, 'w').write(value)

    def delete(self, key):
        if key == self.key:
            self.key = None
            os.remove(cache)

class StreamVision(NSApplication):

    hotKeyActions = {}
    hotKeys = []

    def displayTrackInfo(self, playerInfo=None):
        iTunes = iTunesApp()

        try:
            trackClass = iTunes.current_track.class_()
        except CommandError:
            trackClass = k.property

        trackName = ''
        if trackClass != k.property:
            trackName = iTunes.current_track.name()

        try:
            playerState = iTunes.player_state()
        except CommandError:
            playerState = None # probably iTunes quit
        if playerState != k.playing:
            if playerState != None:
                growlNotify('iTunes is not playing.', trackName)
            turnStereoOff()
            return
        turnStereoOnOrOff()
        if trackClass == k.URL_track:
            if amuaPlaying():
                if playerInfo is None: # Amua displays it itself
                    AmuaApp().display_song_information()
                return
            url = iTunes.current_stream_URL()
            kw = {}
            if url != k.missing_value and url.endswith('.jpg'):
                try:
                    response, content = self.http.request(url)
                except Exception, e:
                    import sys
                    print >> sys.stderr, 'Request for album art failed:', e
                else:
                    if response['content-type'].startswith('image/'):
                        file(self.imagePath, 'w').write(content)
                        kw['image_from_location'] = self.imagePath
            growlNotify(cleanStreamTitle(iTunes.current_stream_title()),
                        cleanStreamTrackName(trackName), **kw)
            return
        if trackClass == k.property:
            growlNotify('iTunes is playing.', '')
            return
        kw = {}
        # XXX iTunes doesn't let you get artwork for shared tracks
        if trackClass != k.shared_track:
            artwork = iTunes.current_track.artworks()
            if artwork:
                try:
                    kw['pictImage'] = artwork[0].data_()
                except CommandError:
                    pass
        growlNotify(trackName + '  ' +
                    u'★' * (iTunes.current_track.rating() / 20),
                    iTunes.current_track.album() + '\n' +
                    iTunes.current_track.artist(),
                    **kw)

    def defaultOutputDeviceChanged(self):
        turnStereoOnOrOff()
        self.displayTrackInfo()

    def goToSite(self):
        iTunes = iTunesApp()
        if iTunes.player_state() == k.playing:
            if amuaPlaying():
                AmuaApp().display_album_details()
                return
            url = iTunes.current_stream_URL()
            if url != k.missing_value:
                if 'radioparadise.com' in url and 'review' not in url:
                    url = radioParadiseURL()
                NSWorkspace.sharedWorkspace().openURL_(NSURL.URLWithString_(url))
                return
        NSBeep()

    def registerHotKey(self, func, keyCode, mods=0):
        hotKeyRef = RegisterEventHotKey(keyCode, mods, (0, 0),
                                        GetApplicationEventTarget(), 0)
        self.hotKeys.append(hotKeyRef)
        self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)] = func
        return hotKeyRef

    def unregisterHotKey(self, hotKeyRef):
        self.hotKeys.remove(hotKeyRef)
        del self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)]
        hotKeyRef.UnregisterEventHotKey()

    def incrementRatingBy(self, increment):
        iTunes = iTunesApp()
        if amuaPlaying():
            if increment < 0:
                AmuaApp().ban_song()
                growlNotify('Banned song.', '', icon_of_application='Amua.app')
            else:
                AmuaApp().love_song()
                growlNotify('Loved song.', '', icon_of_application='Amua.app')
            return
        rating = iTunes.current_track.rating()
        rating += increment
        if rating < 0:
            rating = 0
            NSBeep()
        elif rating > 100:
            rating = 100
            NSBeep()
        iTunes.current_track.rating.set(rating)

    def playPause(self, useStereo=True):
        global needsStereoPowerOn

        iTunes = iTunesApp()
        was_playing = (iTunes.player_state() == k.playing)
        if not useStereo:
            needsStereoPowerOn = False
        if was_playing and amuaPlaying():
            AmuaApp().stop()
        else:
            iTunes.playpause()
        if not was_playing and iTunes.player_state() == k.stopped:
            # most likely, we're focused on the iPod, so playing does nothing
            iTunes.browser_windows[1].view.set(iTunes.user_playlists[its.name=='Stations'][1]())
            iTunes.play()
        if not useStereo:
            return
        if iTunes.player_state() == k.playing:
            turnStereoOnOrOff()
        else:
            turnStereoOff()

    def playPauseFront(self):
        systemEvents = app(id='com.apple.systemEvents')
        frontName = systemEvents.processes[its.frontmost == True][1].name()
	if frontName == 'RealPlayer':
	    realPlayer = app(id='com.RealNetworks.RealPlayer')
	    if len(realPlayer.players()) > 0:
		if realPlayer.players[1].state() == k.playing:
		    realPlayer.pause()
		else:
		    realPlayer.play()
		return
	elif frontName == 'VLC':
	    app(id='org.videolan.vlc').play() # equivalent to playpause
	else:
	    self.playPause(useStereo=False)

    def nextTrack(self):
        if amuaPlaying():
            AmuaApp().skip_song()
            return
        iTunesApp().next_track()

    def registerZoomWindowHotKey(self):
        self.zoomWindowHotKey = self.registerHotKey(self.zoomWindow, 42, cmdKey | controlKey) # cmd-ctrl-\

    def unregisterZoomWindowHotKey(self):
        self.unregisterHotKey(self.zoomWindowHotKey)
        self.zoomWindowHotKey = None

    def zoomWindow(self):
        # XXX detect if "enable access for assistive devices" needs to be enabled
        systemEvents = app(id='com.apple.systemEvents')
        frontName = systemEvents.processes[its.frontmost == True][1].name()
        if frontName == 'iTunes':
            systemEvents.processes['iTunes'].menu_bars[1]. \
                menu_bar_items['Window'].menus.menu_items['Zoom'].click()
            return
        elif frontName in ('X11', 'XQuartz', 'Emacs'): # preserve C-M-\
            self.unregisterZoomWindowHotKey()
            systemEvents.key_code(42, using=[k.command_down, k.control_down])
            self.registerZoomWindowHotKey()
            return
        frontPID = systemEvents.processes[its.frontmost == True][1].unix_id()
        try:
            zoomed = app(pid=frontPID).windows[1].zoomed
            zoomed.set(not zoomed())
        except (CommandError, RuntimeError):
            systemEvents.processes[frontName].windows \
                [its.subrole == 'AXStandardWindow'].windows[1]. \
                buttons[its.subrole == 'AXZoomButton'].buttons[1].click()

    def finishLaunching(self):
        super(StreamVision, self).finishLaunching()

        caches = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
                                                     NSUserDomainMask, True)[0]
        cache = os.path.join(caches, 'StreamVision')
        self.http = httplib2.Http(OneFileCache(cache), 5)
        self.imagePath = os.path.join(cache, 'image')

        self.registerHotKey(self.displayTrackInfo, 100) # F8
        self.registerHotKey(self.goToSite, 100, cmdKey) # cmd-F8
        self.registerHotKey(self.playPause, 101) # F9
        self.registerHotKey(lambda: iTunesApp().previous_track(), 109) # F10
        self.registerHotKey(self.nextTrack, 103) # F11
        self.registerHotKey(lambda: self.incrementRatingBy(-20), 109, shiftKey) # shift-F10
        self.registerHotKey(lambda: self.incrementRatingBy(20), 103, shiftKey) # shift-F11
        self.registerZoomWindowHotKey()
        NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, self.displayTrackInfo, 'com.apple.iTunes.playerInfo', None)
        try:
            import HIDRemote
            HIDRemote.connect()
        except ImportError:
            print "failed to import HIDRemote (XXX fix - on Intel)"
        except OSError, e:
            print "failed to connect to remote: ", e

        set_default_output_device_changed_callback(
            self.defaultOutputDeviceChanged)
        turnStereoOnOrOff()

    def sendEvent_(self, theEvent):
        eventType = theEvent.type()
        if eventType == NSSystemDefined and \
               theEvent.subtype() == kEventHotKeyPressedSubtype:
            self.hotKeyActions[theEvent.data1()]()
        elif eventType == NSApplicationDefined:
            key = theEvent.data1()
            if key == kHIDUsage_Csmr_ScanNextTrack:
                self.nextTrack()
            elif key == kHIDUsage_Csmr_ScanPreviousTrack:
                iTunesApp().previous_track()
            elif key == kHIDUsage_Csmr_PlayOrPause:
                self.playPauseFront()
        super(StreamVision, self).sendEvent_(theEvent)

if __name__ == "__main__":
    growlRegister()
    AppHelper.runEventLoop()
    try:
        HIDRemote.disconnect()
    except:
        pass
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from PIL import Image, ImageDraw, ImageFont

class String2emoji(object):
    """docstring for String2emoji"""
    def __init__(self, argText,argFontName,argFontColor = (0,0,0)):
        self.textList = argText
        self.fontName = argFontName
        self.backColor = (255,255,255,0)
        self.imageSize = (128,128)
        self.fontColor = argFontColor

    def getFont(self,size):
        return ImageFont.truetype(self.fontName, size, encoding='utf-8')

    def setBackColor(self,color):
        self.backColor = color

    def setFontColor(self,color):
        self.fontColor = color
    def cutEffectiveRange(self,text,wMax,hMax):
        for i in range(hMax,hMax*2):
            font = self.getFont(i)
            w, h = font.getsize(text)
            img = Image.new("RGBA",(w,h),self.backColor)
            draw = ImageDraw.Draw(img)
            draw.text((0,0), text.decode('mbcs'), fill=self.fontColor, font=font)
            x0 = self.stringOverBorderX(img,text,font,h)
            y0 = self.stringOverBorderY(img,text,font,w)
            x1 = self.stringUnderBorderX(img,text,font,w,h) + x0
            y1 = self.stringUnderBorderY(img,text,font,h,w) + y0
            if ((x1 >= wMax-5) and (x1 < wMax)) or ((y1 >= hMax-5) and (y1 < hMax)) :
                return (i,x0,y0,x1,y1);
    def stringOverBorderX(self,img,text,font,h):
        for x in range(0,-127,-1):
            limitFlag = 0
            for i in range(0,h-1):
                color = img.getpixel((-x,i))
                if color != self.backColor:
                    limitFlag = 1;
            if limitFlag > 0:
                return x
    def stringOverBorderY(self,img,text,font,w):
        for y in range(0,-127,-1):
            limitFlag = 0
            for i in range(0,w-1,2):
                color = img.getpixel((i,-y))
                if color != self.backColor:
                    limitFlag = 1;
            if limitFlag > 0:
                return y
    def stringUnderBorderX(self,img,text,font,w,h):
        for cx in range(w-1,0,-2):
            for cy in range(0,h-1):
                color = img.getpixel((cx,cy))
                if color != self.backColor:
                    return cx;
    def stringUnderBorderY(self,img,text,font,h,w):
        for cy in range(h-1,0,-1):
            for cx in range(0,w-1,2):
                color = img.getpixel((cx,cy))
                if color != self.backColor:
                    return cy;

    def getEmoji(self):
        img = Image.new("RGBA",self.imageSize,self.backColor)
        draw = ImageDraw.Draw(img)
        l = len(self.textList)

        for i in range(0,l):
            img_str = Image.new("RGBA",(len(self.textList[i])*64,128),self.backColor)
            draw = ImageDraw.Draw(img_str)
            (size,x0,y0,x1,y1) = self.cutEffectiveRange(self.textList[i],len(self.textList[i])*64,128/l)
            #(size,x0,y0,x1,y1) = self.cutEffectiveRange(self.textList[i],256,128/l)
            font = self.getFont(size)
            draw.text((x0,y0), self.textList[i].decode('mbcs'), fill=self.fontColor, font=font)
            img_str.crop((0,0,x1,y1))
            if x1 > 128:
                img_str = img_str.transform(img_str.size,Image.AFFINE,(x1/128.0,0,0,0,1,0),Image.BICUBIC)
                image_paste_x = 0;
            else:
                image_paste_x = (128-x1)/2
            if l != 1:
                img.paste(img_str,(image_paste_x,(128/l)*i))
            else:
                img.paste(img_str,(image_paste_x,(128-y1)/2))
        return img
<commit_msg>python 3.5で動くように改変<commit_after># -*- coding: utf-8 -*-
from PIL import Image, ImageDraw, ImageFont

class String2emoji(object):
    """docstring for String2emoji"""
    def __init__(self, argText,argFontName,argFontColor = (0,0,0)):
        self.textList = argText
        self.fontName = argFontName
        self.backColor = (255,255,255,0)
        self.imageSize = (128,128)
        self.fontColor = argFontColor

    def getFont(self,size):
        return ImageFont.truetype(self.fontName, size, encoding='utf-8')

    def setBackColor(self,color):
        self.backColor = color

    def setFontColor(self,color):
        self.fontColor = color
    def cutEffectiveRange(self,text,wMax,hMax):
        for i in range(hMax,hMax*2):
            font = self.getFont(i)
            w, h = font.getsize(text)
            img = Image.new("RGBA",(w,h),self.backColor)
            draw = ImageDraw.Draw(img)
            draw.text((0,0), text, fill=self.fontColor, font=font)
            x0 = self.stringOverBorderX(img,text,font,h)
            y0 = self.stringOverBorderY(img,text,font,w)
            x1 = self.stringUnderBorderX(img,text,font,w,h) + x0
            y1 = self.stringUnderBorderY(img,text,font,h,w) + y0
            if ((x1 >= wMax-5) and (x1 < wMax)) or ((y1 >= hMax-5) and (y1 < hMax)) :
                return (i,x0,y0,x1,y1);
    def stringOverBorderX(self,img,text,font,h):
        for x in range(0,-127,-1):
            limitFlag = 0
            for i in range(0,h-1):
                color = img.getpixel((-x,i))
                if color != self.backColor:
                    limitFlag = 1;
            if limitFlag > 0:
                return x
    def stringOverBorderY(self,img,text,font,w):
        for y in range(0,-127,-1):
            limitFlag = 0
            for i in range(0,w-1,2):
                color = img.getpixel((i,-y))
                if color != self.backColor:
                    limitFlag = 1;
            if limitFlag > 0:
                return y
    def stringUnderBorderX(self,img,text,font,w,h):
        for cx in range(w-1,0,-2):
            for cy in range(0,h-1):
                color = img.getpixel((cx,cy))
                if color != self.backColor:
                    return cx;
    def stringUnderBorderY(self,img,text,font,h,w):
        for cy in range(h-1,0,-1):
            for cx in range(0,w-1,2):
                color = img.getpixel((cx,cy))
                if color != self.backColor:
                    return cy;

    def getEmoji(self):
        img = Image.new("RGBA",self.imageSize,self.backColor)
        draw = ImageDraw.Draw(img)
        l = len(self.textList)

        for i in range(0,l):
            img_str = Image.new("RGBA",(len(self.textList[i])*128,128),self.backColor)
            draw = ImageDraw.Draw(img_str)
            (size,x0,y0,x1,y1) = self.cutEffectiveRange(self.textList[i],len(self.textList[i])*64,int(128/l))
            #(size,x0,y0,x1,y1) = self.cutEffectiveRange(self.textList[i],256,128/l)
            font = self.getFont(size)
            draw.text((x0,y0), self.textList[i], fill=self.fontColor, font=font)
            img_str.crop((0,0,x1,y1))
            if x1 > 128:
                img_str = img_str.transform(img_str.size,Image.AFFINE,(x1/128.0,0,0,0,1,0),Image.BICUBIC)
                image_paste_x = 0
            else:
                image_paste_x = int((128-x1)/2)
            if l != 1:
                img.paste(img_str,(image_paste_x,int((128/l)*i)))
            else:
                img.paste(img_str,(image_paste_x,int((128-y1)/2)))
        return img
<|endoftext|>


<commit_before>"""Determine the generator format"""
from collections import ChainMap

DEFAULT_FORMAT = {
    'domain': 'example.com',
    'app': '{repo}{project}',
    'dns_elb': '{repo}.{project}.{env}.{domain}',
    'dns_instance': '{repo}{project}-xx.{env}.{domain}',
    'iam_base': '{project}_{repo}',
    'iam_user': '{project}_{repo}',
    'iam_group': '{project}',
    'iam_role': '{project}_{repo}_role',
    'iam_policy': '{project}_{repo}_policy',
    'iam_profile': '{project}_{repo}_profile',
    's3_bucket': 'archaius-{env}',
    's3_bucket_path': '{project}/{repo}{project}',
    's3_archaius_name': 'archaius-{env}/{project}/{repo}{project}/',
    'jenkins_job_name': '{project}_{repo}',
    'git_repo': '{raw_project}/{raw_repo}',
    'git_repo_qe': '{raw_project}/{raw_repo}-qa',
    'git_repo_configs': '{raw_project}/{raw_repo}-config',
}

class Formats(object):

    def __init__(self, config={}):
        self.config = ChainMap(config, DEFAULT_FORMAT)

    def get_formats(self):
        return self.config

    def __getitem__(self, key):
        return self.config[key]
<commit_msg>Add Chainmap support for python2<commit_after>"""Determine the generator format"""
try:
    from collections import ChainMap
except ImportError:
    from ConfigParser import _Chainmap as ChainMap

DEFAULT_FORMAT = {
    'domain': 'example.com',
    'app': '{repo}{project}',
    'dns_elb': '{repo}.{project}.{env}.{domain}',
    'dns_instance': '{repo}{project}-xx.{env}.{domain}',
    'iam_base': '{project}_{repo}',
    'iam_user': '{project}_{repo}',
    'iam_group': '{project}',
    'iam_role': '{project}_{repo}_role',
    'iam_policy': '{project}_{repo}_policy',
    'iam_profile': '{project}_{repo}_profile',
    's3_bucket': 'archaius-{env}',
    's3_bucket_path': '{project}/{repo}{project}',
    's3_archaius_name': 'archaius-{env}/{project}/{repo}{project}/',
    'jenkins_job_name': '{project}_{repo}',
    'git_repo': '{raw_project}/{raw_repo}',
    'git_repo_qe': '{raw_project}/{raw_repo}-qa',
    'git_repo_configs': '{raw_project}/{raw_repo}-config',
}

class Formats(object):

    def __init__(self, config={}):
        self.config = ChainMap(config, DEFAULT_FORMAT)

    def get_formats(self):
        return self.config

    def __getitem__(self, key):
        return self.config[key]
<|endoftext|>


<commit_before><commit_msg>Move OptionDescriptor classes down<commit_after><|endoftext|>