iTunes Plugin

Do you have questions about writing plugins or scripts in Python? Meet the coders here.
User avatar
jitterjames
Experienced User
Posts: 677
Joined: Thu Aug 13, 2009 4:36 pm
Location: Quebec, Canada
Contact:

iTunes Plugin

Post by jitterjames » Tue Aug 18, 2009 3:09 pm

I'd like to create a plugin to control itunes. It seems pretty easy to set up the initial basic commands. I have a working prototype that performs: play, stop, pause, and get/set volume.

so here are my questions.

1 - Since it is so easy, why has no one done it before. Is there some legal reason?
2 - How do I go about making it available for others to test and contribute?

in terms of help that I need right away...

1 - How do i generate the strings that define the icon?

thanks!

stottle
Plugin Developer
Posts: 636
Joined: Sun Apr 26, 2009 10:59 pm

Re: iTunes Plugin

Post by stottle » Tue Aug 18, 2009 4:28 pm

jitterjames wrote: 1 - Since it is so easy, why has no one done it before. Is there some legal reason?
2 - How do I go about making it available for others to test and contribute?
I only use iTunes on my PC, not my HTPC, and only to buy occasional songs. I can't imagine any legal problems, you're just sending input to the program.

To make it available, tar up your plugin's directory and post it either here or in the General forum. Once Bitmonster gets release 3.8 out, there should be a way to make your plugin more easily loadable, but for now the best way is to make it available to forum users.
jitterjames wrote:1 - How do i generate the strings that define the icon?
Try this.

Brett

User avatar
jitterjames
Experienced User
Posts: 677
Joined: Thu Aug 13, 2009 4:36 pm
Location: Quebec, Canada
Contact:

Re: iTunes Plugin

Post by jitterjames » Wed Aug 19, 2009 3:15 pm

OK, I've got a very basic itunes plugin underway. You can play, pause, stop, get some track info and set the volume.

As long as you have itunes installed I think you just need to unzip to the plugins folder and it will work. I have only tested with iTunes 8.2.1.6

There's a lot of functionality available from the dll that I haven't tapped into yet.
I'd appreciate feedback on best practices. Let me know if I'm doing anything stupid in the code. I don't really know much python and I mostly hack my way through object oriented stuff.

I have one particular question about the code if someone can help.

here's a typical action:
#====================================================================
class GetTitle(eg.ActionClass):
name = "Get Title"
description = "Get Current Playing Song Title."
def __call__(self):
iTunes = win32com.client.gencache.EnsureDispatch("iTunes.Application")
return iTunes.CurrentTrack.Name

the line: iTunes = win32com.client... is repeated in pretty much every class or function (whatever it's called). Is there a way to define this globally once?

thanks!
itunes.zip
first version - basic functions only
(16.41 KiB) Downloaded 725 times
Last edited by jitterjames on Wed Aug 19, 2009 3:38 pm, edited 1 time in total.

User avatar
jitterjames
Experienced User
Posts: 677
Joined: Thu Aug 13, 2009 4:36 pm
Location: Quebec, Canada
Contact:

Re: iTunes Plugin

Post by jitterjames » Wed Aug 19, 2009 3:19 pm

stottle wrote: I only use iTunes on my PC, not my HTPC, and only to buy occasional songs. I can't imagine any legal problems, you're just sending input to the program.
I don't use iTunes at all and don't like it, but when I show people what I'm working on they invariably ask "does it work with iTunes?", so I'd like to be able to say "ya, sort of".

and it's a way for me to contribute something back to the community while learning.

James

stottle
Plugin Developer
Posts: 636
Joined: Sun Apr 26, 2009 10:59 pm

Re: iTunes Plugin

Post by stottle » Wed Aug 19, 2009 4:02 pm

jitterjames wrote:There's a lot of functionality available from the dll that I haven't tapped into yet.
What dll? Is there an API?
jitterjames wrote:I have one particular question about the code if someone can help.

here's a typical action:
#====================================================================
class GetTitle(eg.ActionClass):
name = "Get Title"
description = "Get Current Playing Song Title."
def __call__(self):
iTunes = win32com.client.gencache.EnsureDispatch("iTunes.Application")
return iTunes.CurrentTrack.Name

the line: iTunes = win32com.client... is repeated in pretty much every class or function (whatever it's called). Is there a way to define this globally once?
win32com means that you are talking to iTunes through a COM (Component Object Model) interface. I did something similar with my J River plugin. I think the best practice is to create a new thread that stays in contact with iTunes' COM server.

For the J River plugin, I actually created two classes (may or may not be best practices). One, which starts when the plugin loads, will connect if the server is available, but won't tell the server to start if it isn't. The other, which is used to configure actions that need to connect to the server (selecting a playlist for instance), will start the server if it isn't open, then close the server when finished.

Here's the code, if you want to take a look.

Brett
Attachments
__init__.py
Threading/COM example from J River plugin.
(22.02 KiB) Downloaded 383 times

stottle
Plugin Developer
Posts: 636
Joined: Sun Apr 26, 2009 10:59 pm

Re: iTunes Plugin

Post by stottle » Wed Aug 19, 2009 4:34 pm

jitterjames wrote:I'd appreciate feedback on best practices.
I MUCH prefer to use AddActionsFromList, rather than creating a separate action class for each action, if at all possible. If there are only a few distinct actions, I'll make each a class and add all of them, but not when there are a bunch of similar functions. You might want to take a look at that.

Brett

User avatar
jitterjames
Experienced User
Posts: 677
Joined: Thu Aug 13, 2009 4:36 pm
Location: Quebec, Canada
Contact:

Re: iTunes Plugin

Post by jitterjames » Wed Aug 19, 2009 6:37 pm

stottle wrote:
Here's the code, if you want to take a look.

Brett
I'm afraid that's mostly way over my head at the moment. If I continue to access the com the way I am doing, will it have a negative effect? (wasted memory or something?) It certainly seems to react instantly.

If I really need to set up worker threads etc. I'm afraid I'll need more specific help. If that's a big pain for you then don't worry about it. This is not a huge priority for me either. If you did want to make my life easier, maybe just ripping out the parts that don't apply would help, but I'm not sure. I haven't been programming in this type of environment for very long, and I don't know very much about the com system or windows programming in general.

stottle
Plugin Developer
Posts: 636
Joined: Sun Apr 26, 2009 10:59 pm

Re: iTunes Plugin

Post by stottle » Wed Aug 19, 2009 8:14 pm

jitterjames wrote:If I continue to access the com the way I am doing, will it have a negative effect? (wasted memory or something?) It certainly seems to react instantly.
IIRC, if iTunes isn't running, you will start the service. Then when the action is finished, the service will stop. Repeat for each action. It isn't a problem if the app is up and running, only if it isn't.
jitterjames wrote:If I really need to set up worker threads etc. I'm afraid I'll need more specific help. If that's a big pain for you then don't worry about it. This is not a huge priority for me either. If you did want to make my life easier, maybe just ripping out the parts that don't apply would help, but I'm not sure. I haven't been programming in this type of environment for very long, and I don't know very much about the com system or windows programming in general.
No problem. I'm interested, but I've got higher priorities right now. The threading isn't bad once you get a handle on it. If I have time, I may make a shell threaded plugin with a few commands that you could add to. Does that work for you?

Also, is there an api for the dll?

Brett

User avatar
jitterjames
Experienced User
Posts: 677
Joined: Thu Aug 13, 2009 4:36 pm
Location: Quebec, Canada
Contact:

Re: iTunes Plugin

Post by jitterjames » Fri Aug 21, 2009 10:07 pm

If you could create a shell for me to add to that would be perfect. If not, I will probably figure it out eventually but it's not really a priority for me either, and I'm currently learning about a whole bunch of different programming related stuff so my focus for totally new stuff is a bit fuzzy.

As for the API question: I think the answer is yes. If you have itunes installed, then the calls I make in my plugin:

iTunes = win32com.client.gencache.EnsureDispatch("iTunes.Application")
iTunes.Play()

just work. In visual studio you can add a reference to the com object called "iTunes 1.12 Type library" whose path is ...\iTunes\iTunes.exe

add the namespace like this
using iTunesLib;
...then...
iTunesApp iTunes = new iTunesLib.iTunesApp();
...
iTunes.Play();

Of course you can do much more, but I haven't gone that far into it. I do know for certain that you can iterate the playlist gathering info, but I assume that there are ways to generate playlists as well.

If you are looking for a programming reference this is probably it (see attached)... it looks fairly comprehensive. You can probably find more if you google "itunes sdk".
Attachments
iTunesCOM.rar
(243.8 KiB) Downloaded 470 times

User avatar
jitterjames
Experienced User
Posts: 677
Joined: Thu Aug 13, 2009 4:36 pm
Location: Quebec, Canada
Contact:

Re: iTunes Plugin

Post by jitterjames » Fri Aug 21, 2009 10:16 pm

stottle wrote: IIRC, if iTunes isn't running, you will start the service. Then when the action is finished, the service will stop. Repeat for each action. It isn't a problem if the app is up and running, only if it isn't.
I'm not really sure what you mean. If I generate any of my commands when iTunes isn't running, then itunes starts and it stays open.

I'm not really sure what the "service" is in this case. It may be stopping but iTunes isn't. Subsequent commands are being called when the app is open, which you said is not a problem. Is something bad happening on that first call when iTunes is not open, or were you thinking that iTunes would close itself right away?

Anyway I know nothing about this stuff so I trust your judgment, and if you want to make a shell for me to fill out that's cool.

James

P.S. I don't know what IIRC is.

stottle
Plugin Developer
Posts: 636
Joined: Sun Apr 26, 2009 10:59 pm

Re: iTunes Plugin

Post by stottle » Sat Aug 22, 2009 12:40 am

jitterjames wrote:
stottle wrote: IIRC, if iTunes isn't running, you will start the service. Then when the action is finished, the service will stop. Repeat for each action. It isn't a problem if the app is up and running, only if it isn't.
I'm not really sure what you mean. If I generate any of my commands when iTunes isn't running, then itunes starts and it stays open.

I'm not really sure what the "service" is in this case. It may be stopping but iTunes isn't. Subsequent commands are being called when the app is open, which you said is not a problem. Is something bad happening on that first call when iTunes is not open, or were you thinking that iTunes would close itself right away?

Anyway I know nothing about this stuff so I trust your judgment, and if you want to make a shell for me to fill out that's cool.

James

P.S. I don't know what IIRC is.
IIRC = If I remember correctly

COM objects are supposed to reference count when a program attached. When the program detaches, the count drops by one. When the count goes to zero, the object is supposed to shut itself down. If iTunes stays open, there's probably no problem at all. For the J River plugin, starting and stopping wasted resources. Sounds like that isn't a problem in this case. Either way, I'll see if I can put together a shell this weekend.

Brett

stottle
Plugin Developer
Posts: 636
Joined: Sun Apr 26, 2009 10:59 pm

Re: iTunes Plugin

Post by stottle » Sat Aug 22, 2009 11:12 pm

James,

Here's the shell for you. The main plugin class starts and stops the thread that holds the COM interface to iTunes. It turns out that the iTunes COM will emit events when a song starts and stops, which I attached to in order to generate a TrackChanged event in EG. Waiting for COM events is certainly something you want to do in a new thread.

The first problem I encountered is that since EnsureDispatch will start iTunes, iTunes would start as soon as EventGhost, which isn't what I wanted (I assume you don't either?). So I added a Start action, which will run iTunes if it isn't already started. Right now, if you run an action (Play) and iTunes isn't running, it won't start it, it will say "iTunes isn't running". I prefer this. If you want "Play" to start iTunes, just use the same code I used for Start() in the other eg.ActionClass classes. Or, better yet, make it an option in the plugin settings.

Rather than make an ActionClass for Play, Pause, etc, I created StdCall(). When the ACTIONS list (end of the file) is passed to AddActionsFromList,

Code: Select all

ACTIONS = (
(StdCall, 'Play', 'Play', 'Play', 'Play'),
)
will create an instance of the StdCall class, with self.value set to "Play".

The line

Code: Select all

getattr(iTunes,self.value)()
will do the following:
getattr(iTunes,"Play") will evaluate to iTunes.Play, which is then called by the parentheses at the end.

So now all I have to do is add

Code: Select all

ACTIONS = (
(StdCall, 'Play', 'Play', 'Play', 'Play'),
(StdCall, 'Pause', 'Pause', 'Pause', 'Pause'),
)
and I have both a play and a pause action.

There are also ActionClass's for StartApp, ToggleAction and SetVolume (the last mostly because it needed a Configure method). Hopefully those make sense based on the explanation above. The only other trick is that the eg.ActionClass classes call self.plugin.workerThread.CallWait, which makes the actions run in the thread created for the COM object.

One last thing. In ACTIONS, the 2nd parameter is the class name. There should be no spaces for the class name. Also, where it makes sense, if you could use the any of the button names from the MCE remote, then those buttons would automatically work in my EventGhost Beta version. Here's the list of MCE Buttons

Code: Select all

Ok, Back, Left, Right, Up, Down, VolumeUp, VolumeDown, ChannelUp, ChannelDown, Mute, Start, Num0, Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, Teletext, Red, Green, Yellow, Blue, Escape, Enter, Power, Play, Record, Pause, Stop, Skip, Replay, Guide, Recorded_TV, Details, LiveTV, DVDMenu, Forward, Rewind, TV, Music, Pictures, Videos, Star, Pound, Radio, Aspect, Audio, Subtitle
Good luck,
Brett
Attachments
__init__.py
threaded iTunes plugin example
(17.89 KiB) Downloaded 360 times

User avatar
jitterjames
Experienced User
Posts: 677
Joined: Thu Aug 13, 2009 4:36 pm
Location: Quebec, Canada
Contact:

Re: iTunes Plugin

Post by jitterjames » Sun Aug 23, 2009 10:35 pm

It's slow going because I'm a bit lost, but I've made some progress so here is what I've got so far.

J
Attachments
__init__.py
(22.25 KiB) Downloaded 361 times

stottle
Plugin Developer
Posts: 636
Joined: Sun Apr 26, 2009 10:59 pm

Re: iTunes Plugin

Post by stottle » Sun Aug 23, 2009 11:35 pm

Looking good. Only suggestion I have is that AddActionsFromList can generate subfolders for you. So you could do this:

Code: Select all

self.AddActionsFromList(ACTIONS)
with

Code: Select all

ACTIONS = (
(eg.ActionGroup, 'Text.Grp1Name', 'Text.Grp1Descr', 'Text.Grp1Descr',(
<group1 actions>
)),
(eg.ActionGroup, 'Text.Grp2Name', 'Text.Grp2Descr', 'Text.Grp2Descr',(
<group2 actions>
)),
(eg.ActionGroup, 'Text.Grp3Name', 'Text.Grp3Descr', 'Text.Grp3Descr',(
<group3 actions>
)),
)
No problem if you don't like that, I just like being able to make the changes in one place.

Brett

User avatar
jitterjames
Experienced User
Posts: 677
Joined: Thu Aug 13, 2009 4:36 pm
Location: Quebec, Canada
Contact:

Re: iTunes Plugin

Post by jitterjames » Thu Aug 27, 2009 2:40 pm

I added the ability to load a playlist by name.
__init__.py
(24.18 KiB) Downloaded 503 times

Post Reply