=== nhandler_ is now known as nhandler === Jamie is now known as Guest17177 [06:51] hi im writing some documentation. id like to explain what an upstream is, in a clear manner. could someone please help? [06:59] zubin71: I can't find a nice definition from a quick wiki search. Hunt up jcastro in #ubuntu-community-team : he tends to have good answers to this class of question. [07:00] persia, ok... thankx! :) === tux_race1 is now known as tux_racer [13:21] let's hack [13:21] ? [13:25] black_aprentice: You might be early for a class. You're looking for the opportunistic developer stuff? [13:26] yup, but I already got the new that it starts at 1 pm in my time here [13:29] black_aprentice: You might want to hang out in #ubuntu-app-devel in the meantime. That's the all-day-every-day place for discussions about creating apps. [13:29] persia: already there, but no activity at this time also [13:30] Oh well :( === mohi_away is now known as mohi1 [15:33] was isn nu eigentlich? [16:00] class time? [16:00] time to talk about goocanvas? === ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - http://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi || Event: Ubuntu Opportunistic Developer Week - Current Session: Gooey Graphics with GooCanvas - Rick Spencer - Instructor: rickspencer3 || Questions in #ubuntu-classroom-chat [16:00] okay, let us rock [16:01] as usual I have put my class notes on the wiki: [16:01] https://wiki.ubuntu.com/UbuntuOpportunisticDeveloperWeek/GooCanvas [16:01] I'll be using python and pygoocanvas for code samples to talk about goocanvas today [16:01] There is good reference documentaion for goocanvas: [16:01] http://people.gnome.org/~gianmt/pygoocanvas/ [16:01] llive it, learn it, love it ;) [16:01] Unfortunately, it can be a bit tough to find code samples and tutorials [16:01] :( [16:02] so, let's take a look [16:02] So what is a goocanvas? [16:02] A goocanvas is a 2d composing surface [16:02] You can use it to make pretty much any kind of "image" [16:02] It's kind of like an api around a drawing program [16:02] So you can have a ton of fun using a goocanvas, because you are pretty much freed from the constraints of a widget library in creating your UI [16:03] goocanvas is cairo under the covers [16:03] and is designed to easily integrate into your gtk app [16:03] so, before we get into specifics [16:03] questions? [16:03] and note, I will be trying Classbot for the first time, so bear with me for a bit [16:04] no questions? [16:04] eviltwin asked: is goo canvas considered the "approved" replacement to libgnomecanvas? [16:04] I don't know [16:04] I like goocanvas for it's capabilities [16:06] setimike asked: Is goocanvas sort of like glade? [16:06] not at all [16:07] glade is a tool which creates XML files that describe gtk layouts [16:07] goocanvas is a widget which you embed in your program and then use as a drawing surface [16:07] let's take a look [16:07] nadako asked: is it some OO-wrapper for basic cairo drawing things? [16:07] yes [16:07] luismmontielg asked: How do we get pygoocanvas? [16:08] from the repos [16:08] damo12 asked: Can glade add a widget area for pygoocanvas? [16:08] I don't think there is a way to do that [16:08] but I will show you how to add it with code [16:08] So let's add a goocanvas to a pygtk app [16:08] Add it just like a normal pygtk widget [16:09] here's some code [16:09] self.__goo_canvas = goocanvas.Canvas() [16:09] self.__goo_canvas.set_size_request(640, 480) [16:09] self.__goo_canvas.show() [16:09] Be sure to set the size, otherwise it defaults to 1000,1000, it does not default to [16:09] the size alloted to it in your window. [16:09] this is different than normal gtk.Widgets where size is managed for you [16:09] Handle window resizing to resize your goocanvas as well [16:09] For example, if your goocanvas is in a VBox, you can do this: [16:10] rect = self.builder.get_object("vbox2").get_allocation() [16:10] self.__goo_canvas.set_bounds(0,0,rect.width,rect.height) [16:10] store a reference to the root item for your goocanvas, you'll need it later often [16:10] self.__root = self.__goo_canvas.get_root_item() [16:10] The "root" is kinda like the root of an item tree in XML [16:11] so if you do this, you'll have a goocanvas in your app, ready to rock [16:11] questions? [16:11] ok [16:12] let's look at adding things to your goocanvas surface [16:12] I got into goocanvas while working on photobomb [16:12] so I have some code that I can use for examples [16:12] so let's take the example of adding an image to your goocanvas [16:13] assuming you want to modify the image and combine it with other drawing items [16:13] Anything that can be added to a goocanvas is an Item. It get's it's capabilities by inheriting from ItemSimple, and by implementing the Item interface. [16:13] plus each items has some extra capabilities [16:13] so look at the reference for the specific item class, as well as Item and ItemSimple [16:13] Let's add an item to the goocanvas to get a look at how it works in general. [16:14] We'll start by adding an image. [16:14] First, you need to get a gtk.pixbux for your image: [16:14] pb = gtk.gdk.pixbuf_new_from_file(path) [16:14] Then you calculate where you want the image to show on the goocanvas. You'll need a top and a left to place most items on a goo canvas. [16:14] For example, to center the image, I do this: [16:14] cont_left, cont_top, cont_right, cont_bottom = self.__goo_canvas.get_bounds() [16:14] img_w = pb.get_width() [16:14] img_h = pb.get_height() [16:14] img_left = (cont_right - img_w)/2 [16:14] img_top = (cont_bottom - img_h)/2 [16:15] I've calculated the top and left of the item [16:15] now I am ready to create it [16:15] Note that I create the Item, but there is nothing like goocanvas.add(item) [16:15] rather, when you create the item, you set it's parent property. [16:15] it's different than most container relationships, like in PyGtk [16:15] The parent property is the root of the goocanvas [16:15] This is why I remember the root [16:16] because I will be using it whenever I create items for the goocanvas [16:16] finally, here's the code to create the image on teh goocanvas: [16:16] goocanvas.Image(pixbuf=pb,parent=self.__root, x=img_left,y=img_top) [16:16] This basic pattern is how you add all other types of items. [16:16] decide where to put the item, and set it's parent property to the root of the goocanvas. [16:16] To remove the item from the goocanvas, you don't tell the goocanvas to remove it [16:17] rather you tell the item to remove itself [16:17] item.remove() [16:17] questions so far? [16:18] no questions? [16:18] alright then [16:18] let's talk a bit about different types of items that you can add [16:18] In my mind, there are really 3 types of items [16:19] nadako asked: why not standard add/remove functions from parent obj? [16:19] I have no idea [16:19] this is just how the API is [16:19] I presume it's because it is a wrapper around a c-api [16:19] as I was saying, [16:19] In my mind, there are really 3 types of items [16:19] first is normal items that you add to draw the stuff you want [16:20] this includes: [16:20] Ellipse, Image, Path, Polyline, Rect, and Text [16:20] then there are Layout and group items include: [16:20] Group, Grid, and Table [16:20] And then there is also Widget. Widget is pretty cool. [16:20] You can add a gtk widget to your goocanvas, but note that it will live in a world seperate from the goocanvas [16:21] gtk.Widgets won't be rendered if you create images form our goocanvas and such [16:21] However, this is a cool way to add in situ editing to your goocanvas [16:21] We'll just be talking about normal items for the rest of this class though [16:21] I'll take some questions now, if you've got 'em [16:21] fagan asked: What formats does GooCanvas support? (png,jpeg,svg..etc) [16:22] ya know, I never really investigated [16:22] I would presume the first two [16:22] in terms of svg, perhaps that would be supported a bit differently, as many of the items are described with svg language [16:22] or can be [16:23] like an elipse in an svg would be turned into an elipse item [16:23] rather than rendered as an image [16:23] I would be interested to see what different things people try and how it works for them [16:23] tm_lv asked: why goocanvas? [16:23] this is a good question [16:23] I used goocanvas for photobomb because: [16:24] 1. it was an easy API [16:24] 2. it integrate well with pygtk [16:24] 3. it had good reference docs [16:24] 4. it had the functionality to do the things I wanted [16:24] for #4 that was stuff like adding text, paths, etc... [16:24] and also the ability to render off images easily [16:25] nadako asked: is there any ready-to-use things like connector lines that connects two elements and follow their positions [16:25] not that I know of [16:25] nadako asked: also, what about interactivity? drag/drop, mouse clicks, etc [16:25] you handle that manually [16:25] I'll mention mouse handling briefly [16:25] late [16:25] r [16:26] ok, so moving on [16:26] So what are some of the things that you do with an item? [16:26] Well, you compose with it. So you scale it, move it, rotate it, change it's z-order and such [16:26] For a lot of things that you want to do with an item, you use set_property and get_property [16:26] this is the same way of interacting with properties in pygtk, but you do lots more of it in goocanvas [16:27] For example, to set the a might make a Text item like this: [16:27] txt = goocanvas.Text(parent=self.__root,text="some text", x=100, y=100, fill_color=self.__ink_color) [16:27] so here I am adding some text and it will say "some text" [16:27] it will be at x,y 100,100 [16:27] and will use the stored ink color [16:27] then change the text in it like this: [16:27] txt.set_property("text","new text") [16:28] if I want to know what the text is, I can say: [16:28] my_string = txt.get_property("text") [16:28] some capabilities are accessed directly [16:28] such as bounds [16:29] but most are accessed like this [16:29] Let's look at colors for a moment. [16:29] There are generally two color properties to work with, stork-color, and fill-color [16:29] stork-color? [16:29] let's call that stoke-color [16:29] :) [16:29] If you've ever used a tool ink inkscape, this will make sense you to [16:29] for something like a rect, stroke-color is the outline of the rectangle, and fill-color is the inside of the rectangle [16:30] before I discuss a bit more about transforming items ... [16:30] any questions? [16:30] nadako asked: so it's only about drawing things? [16:30] sort of [16:30] I was able to use goocanvas to make a pretty functional editing surface in photobomb [16:31] it included dragging, drawing lines with a pen too, and saving pngs [16:31] but essentially, yes, it's a drawing surface [16:31] tm_lv asked: getters and setters seem to be result of crappy bindings. do you know if there is any work done to make them more pythonic? [16:31] I agree [16:31] but I don't know of any more work to make it more pythonic [16:31] while the API is not perfect, it is functional and robust [16:32] ok [16:32] moving on a bit [16:32] You can move, rotate, resize, and skew items [16:32] The APIs for doing this are intuitive, imho [16:32] To grow something by 10% [16:32] : [16:32] item.scale(1.1,1.1) [16:33] that says make it 1.1 times as tall and wide as it is now [16:33] And to shrink it a bit: [16:33] item.scale(.9,.9) [16:33] Note that the items always consider themeselves to be their original size and orientation, so doing this will cause an item to grow twice: [16:33] item.scale(1.1,1.1) [16:33] item.scale(1.1,1.1) [16:34] Now, when you start rotating and skewing items, some pretty confusing stuff can start happening [16:34] Essentially, an item tracks it's own coordinate system, and doesn't much care about the goocanvas's coordinate system [16:34] So if you rotate an item, for example, the coordinate systems are totally out of whack [16:34] So if you pass the x/ys to an item based on the canvas's coordinate system, it can get waaaay weird [16:34] like if you have a an item rotated 180 degrees [16:35] and you tell it to add 100 to it's x [16:35] it will move left! [16:35] because the items doesn't know or care that it is rotated [16:35] Fortunately, goocanvas has some functions on it that just do these transforms for you [16:35] let's say I catch a mouse click event on an item [16:35] and I want to know where on the item the click happened [16:35] well, the click coordinate are reported in the goocanvas's coordinate system, so I need to do a quick calculation to determine where the click happened on the item: [16:36] e_x, e_y = self.__goo_canvas.convert_to_item_space(self.selected_item,event.x,event.y) [16:36] here self.selected_item is the item who's coordinate system I want to convert to [16:36] so now e_x is x from the item [16:37] 's point of view, not the goocanvas point of view [16:37] phew [16:37] this is weird, but you get used to it [16:37] so, good time for some questions if there are any [16:37] tm_lv asked: can you nest items in goocanvas? [16:37] yes, but I haven't done that [16:38] nadako asked: can you give a link to that photobomb app? [16:38] sure, it's in my junk folder [16:39] https://code.edge.launchpad.net/~rick-rickspencer3/+junk/photobomb [16:39] luismmontielg asked: What could be easier to implement, for a small pygtk app, using pyclutter or pygoocanvas? or whats the difference between those 2 [16:39] clutter is also very cool [16:40] but it's a wrapper around OpenGL [16:40] I suspect that for a small 2d surface, goocanvas will be much better [16:40] however, for rendering animations, clutter will be much better [16:40] you can do drag and drop and such on goocanvas with no problem [16:40] and it has some animation support [16:41] by clutter is all about animations, especially in a cool 3d way [16:41] tm_lv asked: following on the previous one, i'd like to insert a shameless plug - contender #3 the hamster graphics library, sprite-based and python pure: http://wiki.github.com/tbaugis/hamster_experiments/ [16:41] shameless plug accepted ;) [16:41] ok [16:41] moving on [16:41] Just a quick word on paths [16:41] A path is essentially a "squiggle" [16:42] It is defined by a string that gets parsed into x,y coords, and then drawn with a bezier curve formula applied [16:42] let's take a look [16:42] the curve stuff is a bit of math that I don't much understand [16:42] but just to get you started, here is a string that describes a scribble [16:42] line_data = "M 4.0 4.0C4.0 4.0 5.0 4.0 5.0 4.0 5.0 4.0 6.0 4.0 6.0 3.0 10.0 1.0 13.0 2.0 9.0 15.0 6.0 36.0 28.0 11.0 28.0 11.0 29.0 11.0 33.0 12.0 33.0 15.0 32.0 19.0 27.0 51.0 27.0 53.0 27.0 54.0 27.0 54.0 27.0 54.0 36.0 49.0 37.0 49.0" [16:42] it just kind of traces some x/y coords [16:43] then I can make a path out of this: [16:43] path = goocanvas.Path(data=line_data, parent=self.__root, line_width=self.__ink_width, stroke_color=self.__ink_color) [16:43] so this will draw the squiggle in the goocanvas [16:43] Now, a path is also useful because you can use it to clip another object [16:44] like you can draw an arbitrary path around an item and use it kind of like a pair of scissorrs [16:44] You don't use a path object for this, just the string [16:44] item.set_property("clip-path",line_data) [16:44] pretty cool! [16:45] any questions about paths and such? [16:45] ok [16:45] let's go on then [16:45] somebody asked about managing mouse movements and such [16:46] In terms of mousing a goocanvas has the normal gtk mouse tracking capabilities [16:46] to track mouse clicks, for example: [16:46] self.__goo_canvas.connect("button_press_event",self.mouse_down) [16:46] so you can also connect to mousedown, mousemove, mouseup, etc... to handle dragging items around [16:47] self.__motion_handler = self.__goo_canvas.connect("motion_notify_event",self.item_moved) [16:47] self.__mouse_up_handler = self.__goo_canvas.connect("button_release_event",self.drag_stop) [16:47] so that's how I handle dragging items in photobomb [16:47] it's a manual process [16:47] questions? [16:48] ok [16:48] let's go on to the last section [16:48] rendering [16:48] a goocanvas can use cairo surfaces to render off snapshots of itself [16:48] So if I want to make a png, I use an image surface [16:48] x, y, w, h = self.__goo_canvas.get_bounds() [16:48] surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(w), int(h)) [16:48] context = cairo.Context(surface) [16:48] context.rectangle(0, 0, 1000, 1000) [16:48] context.set_source_rgb(1, 1, 1) [16:48] context.fill() [16:48] self.__goo_canvas.render(context) [16:48] surface.write_to_png(image_path) [16:48] (I sniped this originally from segphaults grabbersnap code) [16:49] There are other cairo surfaces as well, including a PDF surface [16:49] so you can render to a pdf, for example [16:49] okay, that's the last bit of material I prepared [16:49] any questions about anything? [16:50] quappa1 asked: Can I try photobomb on Karmic? Seems that in your PPA it's only for Lucid. [16:50] I suppose [16:50] I think it should work [16:51] just pull from trunk and give it a try [16:51] ems asked: any chance you could add some goocanvas snippets to acire? [16:51] I suppose [16:51] I don't know when I would get to it [16:51] I would be glad to help someone else who wants to give it a try [16:51] luismmontielg asked: is there any code examples for this? simple working examples? [16:51] not too many [16:52] that's why I wanted to do the class [16:52] photobomb and grabber snap both use it a bit [16:52] so lots of samples there, but no snippets yet that I know of [16:52] fagan asked: will photobomb get into the repo any time soon? :) [16:52] uh [16:52] well [16:52] look at the code and let me know what you think [16:52] there are lots of ideas cram in there, and some pretty bad bugs [16:52] :) [16:53] maybe sometime though [16:53] unless someone wants to take over and finish it off [16:53] no more questions and 7 minutes early! [16:53] you folks were a great class, thanks for coming [16:54] quappa1 asked: photobomb requires something called quickly.prompts. so probably no luck on Karmic? :) [16:54] oh goodness [16:54] yes, that is not in karmic [16:54] but if you are dedicated, you could hack it together [16:54] like pull the quidgets project and stick the code in the photobomb library === mohi_away is now known as mohi1 === ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - http://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi || Current Session: Writing a Rhythmbox plug-in - Stuart Langridge - Instructor: aquarius || Questions in #ubuntu-classroom-chat [17:00] Hi, everyone, I'm Stuart Langridge [17:01] Today I'm going to talk about making a Rhythmbox plugin. [17:01] but first I should say: cool talk about goocanvas, rickspencer3 :) [17:01] If you're using Lernid, you'll see slides with code examples. If you don't have Lernid, don't worry; all the code for this plugin is available, and you can look through it to see what I'm talking about, or you can load the slide deck yourself from http://www.kryogenix.org/code/RBMicroBlog [17:01] Specifically, I'm going to talk about making a Python plugin. If you're hoping for in-depth C knowledge, you're talking to the wrong dude. [17:01] Feel free to ask questions in #ubuntu-classroom-chat, and I'll answer them at the end; if someone could collect them, that'd be great. [17:01] or, actually, I'll use ClassBot :) [17:02] What with this being Opportunistic Developer Week and all, I'm aiming for something which scratches a little itch. [17:02] The itch I want to scratch is explained by a tweet I saw from @mandel_macaque, which said [17:02] [SLIDE 2] [17:02] ♥ Space Oddity by David Bowie #lastfm: http://bit.ly/15zv4 [17:02] and I thought: hey, that's quite cool, he's tweeting songs that he's playing that he likes. [17:02] I'd like to do that. But I don't use last.fm, I use Rhythmbox to play my music. [17:02] So why not have a "tweet this song" button, so I can hit it when a song that I like comes along? [17:02] A Rhythmbox Python plugin comes in two parts: an .rb-plugin file, which describes your plugin, and a Python module. [17:03] [SLIDE 3] [17:03] Our first stage, then, is to set up the plugin itself. [17:03] Create the Rhythmbox plugins folder, which you might already have: [17:03] mkdir -p ~/.gnome2/rhythmbox/plugins [17:03] Now, create a folder for our plugin, which we'll call RBMicroBlog [17:03] mkdir -p ~/.gnome2/rhythmbox/plugins/RBMicroBlog [17:04] In there create a __init__.py file: [17:04] touch ~/.gnome2/rhythmbox/plugins/RBMicroBlog/__init__.py [17:04] and an .rb-plugin file: [17:04] gedit ~/.gnome2/rhythmbox/plugins/RBMicroBlog/RBMicroBlog.rb-plugin [17:04] Your rb-plugin file has a specific format: [17:04] [SLIDE 4] === edouard is now known as edouardp [17:04] [RB Plugin] [17:04] Loader=python [17:04] Module=RBMicroBlog [17:04] IAge=1 [17:04] Name=Microblogging [17:04] Description=Microblog what you're listening to [17:04] Authors=Stuart Langridge [17:04] Copyright=Copyright © 2010 Stuart Langridge [17:05] Website=http://www.kryogenix.org/code/rhythmbox-microblog [17:05] The important lines in that are the two that tell Rhythmbox "this is a Python plugin", and "the module name for this plugin is RBMicroBlog". [17:05] [SLIDE 5] [17:06] Python, as you will (hopefully) know, treats a folder called X with an __init__.py file inside as a module named X. [17:06] So, we have our RBMicroBlog folder, with __init__.py inside, which is therefore a Python module [17:06] and also in that folder a .rb-plugin file which says "this plugin is provided by the RBMicroBlog module". [17:06] Right now, then, you have a plugin which doesn't do anything. Which means it has no bugs, true, but isn't that useful. [17:06] On to editing the Python. Opportunistic development is fun! [17:07] Basically, we want our plugin to do this: [17:07] 1. be a plugin that can be enabled and disabled [17:07] 2. put a "tweet this" button on the toolbar, with the Gwibber icon in it, and a tooltip of "microblog this song" [17:07] 3. when the button is pressed, work out which song is currently playing... [17:07] 4. and use the Gwibber API to microblog a message saying "Listening to: One Vision by Queen" [17:07] So, start with the basics. [17:07] [SLIDE 6] [17:08] A Rhythmbox plugin is built as a subclass of rb.Plugin, named the same as the plugin module. [17:08] So, a basic plugin would look like: [17:08] import rb [17:08] class RBMicroBlog(rb.Plugin): [17:08] def activate(self, shell): [17:08] print "Activate!" [17:08] def deactivate(self, shell): [17:08] print "Deactivate!" [17:08] Your plugin's "activate" function is called when the plugin is loaded, so it can do setup, and "deactivate" is called when the plugin is unloaded. [17:08] So, start Rhythmbox -- your plugin hasn't been enabled yet, so in Edit > Plugins, find "Microblogging", and tick it to turn it on. [17:09] [SLIDE 7] [17:09] You might be thinking: where does the output from my print statement go? [17:09] Rhythmbox hides output from plugins unless you want to see it. So, quit Rhythmbox, and restart it as "rhythmbox -D RBMicroBlog" [17:10] Now, when you tick your Microblogging plugin on and off in Edit > Plugins, you should see output in the terminal: [17:10] (15:34:36) [0x8756028] [RBMicroBlog.activate] RBMicroBlog/__init__.py:9: Activate! [17:10] (15:34:38) [0x8756028] [RBMicroBlog.deactivate] RBMicroBlog/__init__.py:13: Deactivate! [17:10] Now, you've got an .rb-plugin file and a Python module, and it's loaded. That's the basics of every Python plugin. The only hard remaining bit is to, y'know, actually write the code that does what you want. [17:11] On, then, to stage 2: put a "tweet this" button on the toolbar. [17:11] This is about adding some UI, and is documented at http://live.gnome.org/RhythmboxPlugins/WritingGuide#Adding_UI [17:11] There are also more examples at http://live.gnome.org/Rhythmbox%20Plugins/Python%20Plugin%20Examples which is a very useful page. [17:12] If you come across more things that you want to do in Rhythmbox, please add them to that page! [17:12] (Also, adding those Rhythmbox plugin "snippets" to the python-snippets project in Launchpad would be pretty cool.) [17:12] Adding UI requires two stages: first, you define where you want to add the UI with some XML [17:12] [SLIDE 8] [17:12] and then you actually hook up a gtk.ActionGroup to the UI, as defined by the XML [17:12] [SLIDE 9] [17:13] To be honest, you can just borrow that bit from another plugin. Understanding gtk.Actions and gtk.ActionGroups is fine for those that want to, but if you just want to add a toolbar button and a menu item, steal the code from somewhere else (like this plugin). [17:13] Now, you'll have a button on the toolbar, and a menu item in the Tools menu. The button has no icon, though. [17:14] A bit more cookbook programming, now: how to load an icon and add it. The icon we want is /usr/share/pixmaps/gwibber.svg [17:14] So, some code to load that icon and make it available for your button [17:14] [SLIDE 10] [17:15] Again, don't worry too much about understanding this: the way Gtk deals with icons is pretty complicated. Borrow it for your own projects. [17:15] Now, you have a button on the toolbar and a menu item, and we've connected them up to call a function self.microblog. [17:15] Stage 3 is to implement that. [17:15] Handler functions, like this one, will be called with two parameters, "event" and "shell". [17:16] We want our microblog plugin to find out what's currently playing. [17:16] There are three ways you might approach the problem of "what do I do to find out what's playing?" [17:17] You could know already (if you're Jonoathan Matthew, the genius Rhythmbox maintainer) [17:17] (er, Jonathan) [17:17] (er, Jonathan) [17:17] You could look it up in the documentation (there isn't all that much; the pages linked above cover some things, but not lots) [17:17] Or you can do what I do, which is poke around in the Python objects. [17:17] For this, enable the Python Console plugin in Rhythmbox, and then run it from the Tools menu. [17:18] Now you have a Python console which is hooked up to Rhythmbox. You get a variable called "shell" for free, which is the Rhythmbox shell (and you'll notice that this shell is also being passed to your handler function). [17:18] In Python, to list all the properties of an object, use dir(). So, in the Python console, say: dir(shell) [17:18] [SLIDE 11] [17:18] Hm, that "get_player" looks useful. [17:18] So, say: shell.get_player() [17:18] [17:19] OK, and what's a "ShellPlayer"? [17:19] >>> player = shell.get_player() [17:19] >>> dir(player) [17:19] and "get_playing_entry" again looks useful [17:19] >>> player.get_playing_entry() [17:19] [17:20] THe way Rhythmbox is set up is that there's a database, the RhythmDB, and each song in that database is defined by an "entry", of type RhythmDBEntry. [17:20] So, we've got the "entry" for the currently playing song. But an entry doesn't help much; we want the artist and the title. [17:20] Looking at the properties of the entry doesn't help much here, either. [17:20] Fortunately, the documentation comes to the rescue: http://live.gnome.org/Rhythmbox%20Plugins/Python%20Plugin%20Examples#How_do_I_get_the_metadata_details_of_a_song.3F [17:21] So, given an entry, we can get its title with shell.props.db.entry_get(entry, rhythmdb.PROP_TITLE) [17:21] The first attempt at our microblog handler function, then, could look like this: [17:21] def microblog(self, event, shell): [17:21] entry = shell.get_player().get_playing_entry() [17:21] title = shell.props.db.entry_get(entry, rhythmdb.PROP_TITLE) [17:21] artist = shell.props.db.entry_get(entry, rhythmdb.PROP_ARTIST) [17:22] print "Listening to: %s by %s" % (title, artist) [17:22] and that should, when we hit the button, print out the message to the terminal. [17:22] And indeed it does (remember, rhythmbox -D RBMicroBlog !) [17:22] (16:38:44) [0x907a028] [RBMicroBlog.microblog] RBMicroBlog/__init__.py:112: Listening to: You Shook Me All Night Long by AC/DC [17:23] One more thing, though: what if there's nothing playing? [17:23] Well, poking around in dir(shell.get_player()) a bit more in the Python console reveals "get_playing". [17:23] So let's make the button not do anything if we're not playing. [17:23] Just add one new line to the top of the microblog function: [17:23] if not shell.get_player().get_playing(): return [17:24] OK, so now we have a button which prints out our microblog message. The final stage is to use Gwibber to actually post it for us. [17:24] Fortunately, someone's already done the work for us here. There's a snippet in the acire program which shows how to post a message via gwibber. [17:24] (If you don't know about acire, there's a session about it on Thursday; it's a library of useful Python snippets, and it's great.) [17:25] You can see the snippet itself, if you don't use acire, in Launchpad at http://bazaar.launchpad.net/~jonobacon/python-snippets/trunk/annotate/head:/gwibber/sendmessage.py [17:25] So add a couple more lines to our microblog function: [17:25] gw = gwibber.lib.GwibberPublic() [17:25] gw.SendMessage("Listening to: %s by %s" % (title, artist)) [17:25] [SLIDE 12] [17:25] and we're all done! [17:25] This has been an incredibly brief tour of creating a Rhythmbox plugin, which ties into other cool bits of the Ubuntu platform. [17:26] If this has interested you, a few things you might want to try are: [17:26] Make the plugin tell you when it's successfully posted! [17:26] Change the plugin to disable the button and menu item when nothing is playing (you'll want to connect to Rhythmbox's start playing and stop playing signals) [17:26] Make "quickly create rhythmbox-plugin" work so no-one has to remember all the boilerplate [17:26] OK, that's it for the talk. I'll take some questions now. [17:26] strycore_lernid> IconSource, IconSet, IconFactory ... seems a bit confusing , at least to me [17:27] it is. This is why I suggest just copying the code from an existing plugin, like RBMicroBlog :) [17:27] you don't really have to worry about why it works, just that it does [17:27] enli> what PROP stands for? [17:27] "property", as ems confirmed on #ubuntu-classroom-chat [17:27] so PROP_ARTIST is the "artist" property of a RhythmDBEntry (song) [17:29] eviltwin> QUESTION: Surely we should disable the button when playback stops and reenable it when playback starts rather than a dirty, dirty if statement? [17:29] hence the "extra credit" part at the end of the talk ;) [17:29] definitely that would be the way to do it, yes [17:29] but I've tried to keep RBMicroBlog tiny so that it's a simple thing that can be learned from [17:30] if someone wants to trick it up into a proper application, that's a great idea :) [17:30] danyR asked: No way to get the plugin available to the masses? or in vanilla-lucid? just what i was looking for! [17:30] well... [17:30] to do that, you'd need to package it [17:30] you can do that, certainly [17:31] but I'm not the person you want to talk to about that [17:31] one of the reasons that I'd really like to see "quickly create rhythmbox-plugin" is that it could set up the packaging stuff for you as well [17:31] stevec49 asked: For those looking to add plugin support to their own applications, do you think RhythmBox is a good model to follow? [17:32] broadly, yes [17:32] Rhythmbox plugins have a number of special methods (like activate and deactivate, but there are lots more) because there are lots of places in the Rhythmbox startup and usage procedures that plugins might want to plug into. [17:33] I, personally, like plugins to be one file rather than two, so when I implement Python plugin loading for an app, I put the stuff that's in .rb-plugin into the Python plugin itself, as variables. [17:33] because I like single-file deployment of plugins -- it's easier to install a plugin if it's one file that you drop in a folder. [17:33] but that's flat out personal perference on my part :) [17:34] eviltwin asked: is this a plugin that you've written and made available somewhere? if so, where's the project homepage/code repo? [17:34] http://www.kryogenix.org/code/RBMicroBlog [17:34] the plugin code is downloadable from there [17:35] danyR asked: Shouldn't this be available in out-of-the-box Lucid? "What i'm listening to" sharing? [17:36] if someone makes RBMicroBlog better, so that it's worthy of inclusion, sure. You might have missed out on getting it into Rhythmbox in lucid by default, but it would make it into universe, I'm sure, and could them be installed [17:36] oskude asked: does a rhythmbox plugin have access to the audio stream/data ? [17:37] it does, yes; you can work with the underlying gstreamer code. http://live.gnome.org/RhythmboxPlugins/WritingGuide#Adding_elements_to_the_GStreamer_playback_pipeline has details about that. [17:37] stevec49 asked: Is the RhythmBox "shell" itself a python module? [17:37] sort of. [17:38] it's passed to your handler functions, and to your plugin [17:38] and it's available in the Python console [17:38] but it only exists if you're a Rhythmbox plugin [17:38] you can't do "import rb.shell" from a random Python program and get access to control Rhythmbox [17:38] if you want to control Rhythmbox from an outside application, use D-Bus. [17:39] quigs asked: is the UI stuff not included in the package you provided? [17:39] does it not work, then? [17:40] ha. quigs points out there is a bug in the deactivate handler in the plugin :) [17:40] my fault. [17:40] I'll take a look at that later [17:41] Any other questions? [17:41] There is a rhythmbox-plugins package in lucid, which you may already have installed, which contains lots of plugins [17:42] and there are others at http://live.gnome.org/RhythmboxPlugins/ThirdParty [17:42] and http://live.gnome.org/RhythmboxPlugins/WishList lsts some plugins that people want but don't have, if you want to work on something but aren't sure what [17:43] what with Rhythmbox being the default media player, more plugins will make it do more stuff and generally be cooler, which I'm all in favour of :0 [17:43] duanedesign_ asked: Where does the XML file go? [17:44] I normally just put the XML in a string inside my plugin [17:44] since it only defines where a button or menu go, it's normally pretty short [17:45] if you want to put it in a separate file, then put the file in the folder with the plugin, and use shell.find_file("myxmlfile") to get its pathname, that way it'll work no matter where your plugin is installed [17:46] OK, cool, no more questions? [17:47] strycore_lernid asked: It's a bit off topic, but in the third party plugins there are some that say "It is not an Internet radio server", GloveSoap and IceCast can do the job, but is there a way to send the audio stream to a server without opeing ports on a firewall ? [17:47] erm [17:47] there might be. I don't know the answer there, I'm afraid. [17:48] the people in #gstreamer may be able to give you some pointers on whether there are gstreamer elements that can do that, which you could add to the pipeline [17:48] so, we get to finish a little bit early and everyone can go get a cup of tea before kenvandine explains how to *really* use the Gwibber API, beyond the two lines I've just used! [17:49] thanks, all === william is now known as Guest94411 === kenvandine is now known as kenvandine[busy] === ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - http://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi || Event: Ubuntu Opportunistic Developer Week - Current Session: Microblog from your app with the Gwibber API - Ken VanDine - Instructor: kenvandine || Questions in #ubuntu-classroom-chat === kenvandine[busy] is now known as kenvandine [18:15] Yay! [18:16] \o/ [18:16] jcastro, as usual you are my hero [18:16] ok... only 16 minutes late [18:16] ok, we are here to talk about the Gwibber API [18:17] A quick intro to Gwibber [18:17] Gwibber is a social networking client as well as a desktop service [18:17] that can be used to post messages to a variety of services [18:17] facebook, twitter, identi.ca [18:17] etc [18:17] it can also pull feeds from them, images, etc [18:18] the client is just a frontend to the desktop service that does the real heavy lifting [18:18] the desktop service also provides both dbus and python APIs [18:18] as an application developer, you can embed some social networking features into your application pretty easily [18:19] i have some examples prepared and we can browse through them [18:19] first there are some python docs at http://people.canonical.com/~kenvandine/gwibber/docs/html/ [18:19] right now that mostly documents the python api [18:19] we want to generate docs for the DBus API as well [18:20] so if anyone knows how to do that in epydoc, please let me know after the session... :) [18:20] all of the examples are available in bzr at lp:~ken-vandine/+junk/gwibber-api-examples [18:20] bzr branch lp:~ken-vandine/+junk/gwibber-api-examples [18:20] or [18:20] you can get them in the browser as well, i'll provide links as we discuss them [18:21] most common operation would be posting messages of course [18:21] http://people.canonical.com/~kenvandine/gwibber/api-examples/send_message.py.txt [18:21] as you can see the api is quite simple [18:21] import gwibber.lib [18:21] gw = gwibber.lib.GwibberPublic() [18:22] GwibberPublic is the class we provide with all the public python methods [18:22] so most of the examples will use this [18:22] gw.SendMessage("This is a message") [18:22] SendMessage takes a single argument as a string [18:22] which is the content of the message to post [18:23] also note, the method names match the DBus method names [18:23] so it should be pretty easy to discover how to do the same thing from a C, mono, vala, etc app [18:23] via dbus [18:24] i should mention gwibber is broken down into two parts, gwibber and gwibber-service [18:24] the service runs independently of any UI [18:24] and is dbus activated, so if it isn't already running it will start up automatically [18:24] so that is sending a message, pretty easy [18:25] there are some other operations you can do as well [18:25] if you want to tell the service to refresh it's feeds [18:25] you can call Refresh [18:25] http://people.canonical.com/~kenvandine/gwibber/api-examples/refresh.py.txt [18:25] also very simple API [18:25] no arguments, it just tells the service to go refresh itself [18:26] you might want to do something a little more complex [18:26] like perhaps query accounts that are configured and find specific accounts or services [18:27] http://people.canonical.com/~kenvandine/gwibber/api-examples/get_accounts.py.txt [18:27] the GetAccounts method returns json formated string of accounts [18:28] you can then iterate over the accounts and find specific values, one of interest might be "send_enabled" [18:28] or "receive_enabled" [18:28] those tell gwibber which accounts to post to or pull data from [18:28] so back in the SendMessage example, gwibber would post to all accounts that have send_enabled = True [18:29] you may want to find all the accounts that are enabled and display that in your application [18:29] just as an example [18:29] http://people.canonical.com/~kenvandine/gwibber/api-examples/get_services.py.txt [18:29] is very similar [18:29] it queries all services known to gwibber [18:30] some of which you might not have accounts setup for [18:30] and if you want to use gwibber to shorten a url, there is a method for that as well [18:30] http://people.canonical.com/~kenvandine/gwibber/api-examples/shorten.py.txt [18:31] Shorten takes an argument of a string and returns a shortened string using the user's configured preferred shortening service [18:31] is.gd, tinyurl.com, etc [18:32] these are the basic method provided in GwibberPublic [18:32] any questions so far? [18:32] that was the goal :) [18:32] ok [18:32] moving on [18:33] there is also a gtk widget available that you can embed into your application [18:33] and we hope people will create a variety of useful gtk widgets and contribute those back to gwibber [18:34] making it even easier to make your application social :) [18:34] http://people.canonical.com/~kenvandine/gwibber/api-examples/gwibber-widget.py.txt [18:34] i am not going to talk about the part of this that creates the gtk window, etc [18:34] that is just a place holder so you can run the example if you like [18:34] there are just a couple of key lines you need [18:34] from gwibber.lib.gtk import widgets [18:35] that gives you the widgets [18:35] poster = widgets.GwibberPosterVBox() [18:35] GwibberPosterVBox is a VBox that contains a text input, text overlay for character count, send button and toggles for each service [18:36] http://people.canonical.com/~kenvandine/gwibber/api-examples/poster.png [18:37] that is what the widget looks like [18:37] so you could embed that into your application [18:37] so [18:37] poster = widgets.GwibberPosterVBox() [18:37] window.add(poster) [18:37] http://people.canonical.com/~kenvandine/gwibber/api-examples/gwibber-widget.py.txt [18:38] is really all you need [18:38] let me put up the screenshot again [18:38] http://people.canonical.com/~kenvandine/gwibber/api-examples/poster.png [18:38] the toggle buttons on the botton that have the icons for the services [18:38] those are all live and tied into the gwibber service [18:39] so toggling those enables and disables sending to those services desktop wide [18:39] if you are running Lucid, you can see this in action by running gwibber-poster [18:39] another variation there [18:39] http://people.canonical.com/~kenvandine/gwibber/api-examples/gwibber-widget-with-contents.py.txt [18:40] this shows adding some default content [18:40] so if your application has some context about what you are going to post about, like perhaps in the rhythmbox example [18:40] you just marked a song you liked, rated it or whatever [18:41] you could have that pre-populate the text input with information about the song you just rated [18:41] and let the user edit the text if they like and hit Send [18:41] to post it [18:41] poster.input.set_text(contents) [18:41] where contents is a string that gets added [18:42] note: if that text STARTS with a url [18:42] it will get shortened automatically [18:42] i don't think that works if the url isn't at the beginning, but i could be wrong [18:43] our hope here is to get lots of application developers to embed this functionality [18:43] really utilize gwibber as a desktop service [18:43] not just as a gui for viewing/posting tweets :) [18:44] i have just one more area i want to touch on, then we can open up some discussion [18:44] gwibber stores all of it's data in desktopcouch [18:44] a very nice document oriented database [18:45] which also has replication features, so your settings can replicate to other computers either directly through desktopcouch or through ubuntu one [18:45] some features of gwibber aren't exposed (at least yet) through the python or dbus APIs [18:45] things like retrieving message contents [18:45] but it is easy to do with desktopcouch directly [18:45] here is an example [18:46] http://people.canonical.com/~kenvandine/gwibber/api-examples/messages_from_couch.py.txt [18:46] this uses the desktopcouch records API to connect to the database and get records [18:46] that match the record_type for gwibber_messages [18:47] results = messages_db.get_records(record_type = record_type, create_view = False) [18:47] results would be a big json string of all your messages [18:47] which you can then iterate over and get individual messages out [18:47] and various bits of data [18:48] your application could also define views for data your app uses regularly [18:48] so perhaps you want to get all the records that have images [18:48] so you could view all your facebook friend's albums [18:48] the data is pretty easy to get at in desktopcouch [18:48] someone just needs to write an extension for f-spot to make it easy to browse them :) [18:48] and maybe even comment [18:49] ok, i think that is all i have [18:49] questions? comments? [18:49] the widget doesn't provide that, but that would be useful [18:49] it is on my todo list :) [18:50] but won't happen for 2.30, we are in feature freeze already [18:50] next? [18:52] QUESTION: Are the special gtk widgets available in languages other than python? Say I want to use this in an app written in c# and use gtk#? or just plain c? [18:52] not yet [18:52] but for 2.32 (or 3.0, we are following gnome version numbering now) [18:52] i would like to have a libgwibber [18:52] to provide just that [18:53] if there are any C gurus out there and want to help [18:53] please let me know [18:53] ideally we want to move the widgets to C and just create python bindings for it [18:53] QUESTION: how can an user disable an app from using gwibber if that app doesn't have it as a preference? I don't really feel comfortable with random apps being able to send messages out to the public without me having to confirm it first [18:54] hadn't really thought about that [18:54] there isn't really any mechanism for applications register with gwibber to control access [18:54] we'll discuss that next cycle [18:55] well, thanks everyone [18:55] i'll be back for the next session which is showcasing gwibber === ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - http://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi || Event: Ubuntu Opportunistic Developer Week - Current Session: SHOWCASE: Gwibber - Ken VanDine - Instructor: kenvandine || Questions in #ubuntu-classroom-chat [19:03] anything we have to do to get lernid to show the slides? [19:04] [SLIDE 1] [19:05] ok everyone, welcome to our little showcase on Gwibber [19:05] Gwibber is a social networking desktop service and client [19:05] Gwibber has kind of a fun history, started out as just a playground and has really blossomed [19:06] [SLIDE 2] [19:06] Ryan Paul created Gwibber as a tool to learn about pygtk development while writing an article for Ars Technica [19:06] back in 2007 [19:07] and for a while it was really just his little playground for experimenting with new technologies [19:07] research for other articles he wrote [19:07] etc [19:07] i don't think he had any idea it would become quite so popular :) [19:08] Ryan is seg|ars :) [19:08] who is listening in here, so hopefully he can correct me when needed :) [19:08] [SLIDE 3] [19:08] the slides are mostly here as a place to stick some screenshots to show how gwibber has evolved [19:09] which is actually quite from to see, as a long time user [19:09] s/from/fun [19:09] [SLIDE 4] [19:09] this is gwibber in it's first incarnation [19:09] back in 2007 [19:10] mostly all pygtk [19:10] quite simplistic [19:10] [SLIDE 5] [19:10] this was when Ryan was experimenting with using cairo directly to render the messages [19:11] [SLIDE 6] [19:11] Gwibber was starting to look a bit nicer here [19:11] Ryan had moved on to using webkit to render the messages pane [19:11] and styling with css [19:12] it was quite slick looking and easily themed at this point [19:12] and he had just added facebook support [19:12] this was 2008ish [19:13] [SLIDE 7] [19:13] this is from about the same time, maybe even the same version [19:13] shows the accounts dialog [19:14] [SLIDE 8] [19:14] this was the 2.0 era, which is what we were working on getting into Karmic [19:14] but i hate to say 2.0 was never really robust enough [19:14] it was the first version that split out the separate service [19:14] and relied heavily on DBus for all of it's message handling [19:15] which actually proved quite problematic [19:15] but you can see it is starting to look a little differnent now [19:15] [SLIDE 9] [19:15] this is what Gwibber looks like today [19:16] it has come a long way [19:16] the desktop service is now working very well [19:16] the python API is proving to be useful [19:16] as is the DBus API [19:17] there have been quite a bit of buzz around about the new look, which is nice [19:17] [SLIDE 10] [19:17] and now it is included in Lucid Lynx by default [19:17] Gwibber has come a long way! [19:17] thanks seg|ars, you rock! [19:18] Gwibber has matured enough that we are including it in Ubuntu, and in an LTS even [19:18] i don't have screenshots of this [19:18] but if you are running Lucid [19:18] you can see it for yourself [19:18] the Me Menu, up to the left of the Session menu in the top panel [19:19] there is a text entry now [19:19] that posts to Gwibber [19:19] so right from the panel you can fire off a quick post [19:19] http://arstechnica.com/open-source/reviews/2010/03/hands-on-ubuntu-goes-social-gains-me-menu-in-1004-alpha-3.ars [19:19] an article from Ryan about it [19:19] http://static.arstechnica.com/ubuntu1004a3/me-menu.png [19:20] there's a screenshot [19:20] lets talk a little about some of the technology used in the current gwibber and talk about how Gwibber has benefited [19:21] [SLIDE 11] [19:21] python, pygtk and webkit [19:21] Gwibber has been using those since nearly the beginngin [19:21] a bit about webkit [19:22] the messages pane is completely rendered in Webkit which makes themes, etc easy [19:22] and it fits the model well [19:22] one thing to note, which is interesting [19:22] the navigation bar on the left side and the bar to the bottom of the text input with the toggle buttons [19:22] that isn't gtk [19:22] those are all rendered in webkit [19:23] Ryan was having a hard time bending pygtk to do what he needed to do, so he moved all that to webkit and made them look like they belong [19:23] quite nice [19:24] so just something you might want to look at the source for if you ever feel restricted and want to break outside the box [19:24] not saying it is good or bad... it would be nice if pygtk was able to do what he wanted [19:25] also in the 2.30 series Gwibber has moved to desktopcouch for data storage [19:25] so preference, accounts and messages are all stored in desktopcouch [19:25] there are many pluses to this [19:26] one is we get syncing for free, if you pair desktopcouch instances on your local network or if you are an ubuntu one user, your gwibber accounts/settings sync automatically [19:26] so you only have to configure it in one place [19:26] but desktopcouch also helped us solve the robustness problems we had with DBus [19:26] we still use DBus for quite a bit of stuff [19:26] but we never pass data around [19:26] the message data is a pretty complex data structure that used to make dbus tip over [19:27] now we access the message data directly from couch [19:27] desktopcouch also gives us events so we could remove code, which is always good [19:27] for example [19:27] notifications and messaging indicator support [19:28] now instead of the service getting a new message and telling a bunch of different moving pieces to go off and do things [19:28] it just writes it out to the database [19:28] we see the event for a new record added to the database, and we show a notification for it [19:28] removed a bunch of the logic we had in place before [19:29] and let couchdb do the work [19:29] same for adding replies/mentions to the messaging menu [19:29] also the client uses those same events to know when to render the messages pane, etc [19:29] it is used all over the place [19:30] so desktopcouch was a huge win for Gwibber imho [19:30] ok, now lets move on to questions [19:30] QUESTION: Joined later but is there in Lucid with gwibber a daemon that monitors incomming messages (and notifies ofcourse) instead of having Gwibber open all the time? [19:30] yes [19:30] the gwibber-service will run in the background without the client open [19:31] while running, if you have notifications enabled [19:31] it will display them [19:31] etc [19:31] when you launch the client, it will just talk to the existing service that is running [19:31] if you "Quit" the client in the Gwibber menu, it will shutdown the service [19:31] to give users a way to stop it if they like [19:31] but [19:32] if you close the window it doesn't kill the service [19:32] in the next cycle, i would like to make that configurable [19:32] QUESTION: with those bars rendered in webkit, do they look out of place if you change the theme or does it somehow adapt to the colours of your gtk theme? [19:33] they do honor some of the gtk values [19:33] like color and such (i think) [19:33] but for example the icons don't change [19:34] i would like to move to a model where we provide icons that can be over ridden by the theme [19:34] QUESTION: the desktopcouch is restricted but how are the passwords stored in the DB? [19:35] it is stored as a string [19:35] we should move those into the keyring in the next cycle, hopefully [19:36] right now you need keyring access to access couch [19:36] but ideally we shouldn't store them that way anyway [19:37] QUESTION: As many users really don't get along well with microblogging, shouldn't the RSS functionality of gwibber get back and be improved, for them use gwibber, at least, like an rss reader? [19:38] not sure i agree [19:38] i personally don't see RSS reading as social networking, but i know others disagree with me [19:38] i am more keen on growing the use cases [19:38] like photo sharing [19:39] etc [19:39] there are many ways we can utilize gwibber outside of "microblogging" [19:39] i don't really consider Gwibber a microblogging client anymore [19:39] i see it is a social networking aggrigator [19:40] a single mechanism for you and the applications you use more effectively interact with your friends [19:40] like viewing your friend's facebook photo albums, tagging people in photos, commenting on them, etc [19:40] all from inside of f-spot [19:40] instead of through a web interface [19:41] there are many other use cases like that [19:41] which i would like to focus more on [19:42] any more questions? [19:43] ok... so thanks for your time [19:43] i hope everyone enjoys gwibber! === ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - http://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi || Event: Ubuntu Opportunistic Developer Week - Current Session: Building multimedia into your app with GStreamer - Laszlo Pandy - Instructor: laszlok || Questions in #ubuntu-classroom-chat [20:01] Alright I guess that's me [20:02] I'm going to be talking about GStreamer [20:02] I'm not sure how much you guys know already, so if you have any questions or I missed something, please ask in #ubuntu-classroom-chat [20:02] [SLIDE 1] [20:03] Seems the schedule in lernid isn't working [20:04] I'll get a link for those who aren't using lernid as well [20:05] For those that do still see the gwibber slides, is the schedule list loaded? On my lernid neither is there [20:05] Lets do it manually then: http://laszlopandy.com/files/opportunistic-developer-gstreamer.pdf [20:07] Thanks for reporting guys, it must be a server issue [20:08] I'm gonna start now and you can follow along manually from the link above [20:08] First slide! [20:08] So I'm talking about adding gstreamer support to your app [20:08] This means multimedia stuff [20:08] audio and video all that cool jazz [20:09] GStreamer can do pictures too, but most apps use separate libraries for images because of performance [20:09] However I am not talking about playing event sounds like an alert [20:10] those sounds which are really short and you just play, never pause, seek, etc [20:11] GStreamer is overkill for that, and there are better libraries for that (which could be covered in another talk) [20:11] Second slide! [20:12] GStreamer is a framework for putting decoders, filters, encoders, file readers, etc. together [20:12] if you are writing a decoder to integrate with ubuntu you are going to want to know about the framework aspects [20:12] [SLIDE 2] [20:13] but for us, in most cases we only care about GStreamer as a library [20:14] and GStreamer is a very good library, it takes very little code to achieve a great deal in your application [20:14] Slide three! [20:15] GStreamer can automatically detect which plugin to use depending on the protocol (file://, http://, etc) the media container, codec [20:16] you just have to tell it where to get the data and it works like magic [20:17] another important aspect is that it spawns its own threads for each stream and provides a nice interface so we don't have to deal with it [20:17] when you are playing video there is a thread for audio and one for video, but all the signals come in to your main thread like gtk callbacks; nice and clean [20:18] QUESTION: How portable is it? Can I make gstreamer apps for Win/Mac? [20:18] yes you can [20:18] its supports many platforms [20:19] I am part of the Jokosher project which uses GStreamer for an audio multitracker [20:19] and we have been doing windows releases for more than a year i think [20:20] the official releases don't have builds for any platform, but like the GStreamer PPA for ubuntu there is a win-builds which has all the latest releases for windows [20:20] I don't have any experience using it on the Mac, but I know it is supported [20:20] [SLIDE 4] [20:20] okay now we get into the real stuff [20:21] today's talk is mostly a high level usage of gstreamer, but i think it is important to understand a few concepts [20:21] QUESTION: This is a really specific one, and may want to wait, but can I seek into a movie and display a still thumbnail? [20:22] yes, remind me about that at the end and I will tell you specifically how to do it [20:22] So Gstreamer is concerned with moving data [20:22] data flow happens inside a pipeline [20:23] there are a bunch of elements all linked together [20:23] the source(s) provide data to the pipeline, and the sink(s) take it out and put it somewhere [20:24] an example pipeline would start with a file source, move it through a decoder, pass it to audioconvert (which makes sure it is in the right format) and that would pass it to the Pulseaudio sink [20:25] A bin is another important concept. It is just a collection of elements within a pipeline [20:25] you can think of it as a pipeline inside a pipeline [20:25] [SLIDE 5] [20:26] here are some example sources [20:26] gstreamer has lots [20:26] giosrc is important because it provides all the protocols of the gnome virtual file system [20:27] if you run the debugging tool gst-inspect-0.10 with no arguments in the terminal it will tell you alll the plugins you have installed === w1nGNUtz_ is now known as w1nGNUtz [20:27] gst-inspect-0.10 pulsesrc will give you all the info about the pulse audio source [20:28] [SLIDE 6] [20:28] same deal with sinks [20:30] QUESTION: how does changing framerates fit into the pipline? [20:31] since its been brought up i will quickly cover another topic [20:31] the link between each element has a thing called caps (short for capabilities) [20:31] it specifies what kind of data is allowed to be sent from one element to another [20:32] for example pulsesink does not want to receive ogg vorbis data, it wants raw audio [20:32] so we have to feed it through the ogg vorbis decoder first [20:33] it is possible to manually specify the caps of each link, so you can say you want this audio converted to 44100Hz before sending to pulsesink [20:33] in the same way you can force the framerate of a video stream [20:33] but if you get the caps wrong, the elements will complain that they can't link [20:34] so its a bit more advanced, we won't be doing caps today [20:34] [SLIDE 7] [20:34] so here is an example pipeline [20:34] the gstreamer language syntax has the ! meaning link to [20:35] there is a debugging tool which uses this syntax, and you can test out all sorts of cool pipelines [20:35] every can try: [20:35] gst-launch-0.10 audiotestsrc wave=ticks ! pulsesink [20:36] gst-inspect-0.10 audiotestsrc will tell you near the bottom what other types of waves you can use [20:36] [SLIDE 8] [20:37] as I have mentioned there is all sorts of complicated stuff going on in gstreamer [20:37] but playback is a pretty simple use case, so they invented playbin2 [20:38] it is used by totem and many gstreamer apps who just want to play audio and video [20:38] it internally manages almost everything for us [20:39] reading from the right protocol, finding decoders, it even handles subtitles and can do gapless playback [20:39] [SLIDE 9] [20:39] so lets try a really simple playbin example using some python [20:39] http://laszlopandy.com/files/playbin.html [20:40] as you can see in the code, we have to import gst and create a new playbin2 object [20:41] all gstreamer plugins are dynamically loaded, so you have to use the factory to create it [20:41] gst.element_factory_make('playbin2') will fail if the plugin 'playbin2' is not installed [20:41] the next step we set the URI [20:42] then we get the bus, which allows us to monitor the pipeline through signals, just like in GTK [20:42] we can attach a callback to the "message::eos" signal (EOS is end of stream) [20:42] then we set the playbin to the playing state [20:43] there are four states in gstreamer: NULL, READY, PAUSED and PLAYING [20:43] NULL is for when you want to free the resources and destroy the object [20:44] after we set the state we start the mainloop so we get receeve signals [20:44] *receive [20:44] any questions about this code? [20:44] [SLIDE 10] [20:45] QUESTION: what if it's a file not a url? [20:45] do it like file:///home/laszlo/... (it has to be an absolute path) [20:45] QUESTION: can you elaborate on states? what state a pipeline should have if we don't want to destroy it, but rather play next source? [20:46] you can have it in whichever you like [20:46] gstreamer allows the pipeline to be changed while its playing [20:46] though its probably best to move it to paused, switch the uri, and put it back to playing [20:47] QUESTION: what other proteries can we set with playbin.set_property()? And sourcefile is it allway an uri? [20:47] gst-inspect-0.10 playbin2 [20:47] that will tell you about the properties [20:47] playbin2 only deals with URIs, but those can be file:/// ones too [20:47] QUESTION: and if we want to stop playing and restart it from beginning? [20:48] you should set it to PAUSED and seek to the start (seeking is slide 17 if we get there) [20:48] to clarify PAUSED means the same thing as PLAYING except that the data is not moving [20:49] the data is there on the edge, but the damn is closed [20:49] if you have a video in PAUSED you will see the first frame [20:49] QUESTION: does using playbin also handle installing packages for missing codecs like with mp3's? Or that's something that should still be manually programmed? [20:50] i dont think playbin does this itself [20:50] GStreamer will send an error on the pipeline ("missing codec!") [20:51] and you can catch the error and use another module (import gst.pbutils) to launch the codec install window [20:51] okay everyone got that code from slide 10? [20:51] its a GTK window, pretty simpel [20:51] its needs some gstreamer love [20:51] [SLIDE 11] [20:52] http://laszlopandy.com/files/playback_interface.html [20:52] [SLIDE 12] [20:53] http://upload.wikimedia.org/wikipedia/commons/d/df/Hurricane_Connie_1955.ogg [20:53] and back to the code http://laszlopandy.com/files/playback_interface.html [20:54] so we are gonna take the playbin2 example and put it into the GUI class [20:54] just make sure all code in __init__() is before the call to self.main_window.show_all() [20:55] [SLIDE 13] [20:55] on_finish becomes a class method, and we update the GUI when it is called [20:55] whoever was asking about to restart from the beginning, this is it [20:56] seek_simple, FORMAT_TIME, to position 0 [20:56] [SLIDE 14] [20:56] this is straightforward [20:56] in the button handler we either set the state to PLAYING or PAUSED [20:57] right before we do gtk.main_quit() we should set the state to NULL otherwise gstreamer will print an error on the console about resources not being cleaned up properly [20:57] [SLIDE 15] [20:57] if you guys are building this code, it should sorta work now [20:58] QUESTION: the uris in playbin need to be absolute, or I can write them like relative paths for files? [20:58] I have not figured out a way to make file:/// work without absolute paths [20:58] you should get the current working durectory using the os.path.abspath('.') command in python [20:59] [SLIDE 16] [20:59] heres the tricky part, we have to attach the video to our GTK window [20:59] playbin2 has a video-sink property which allows us to switch where it sends the video [21:00] we should wait until the widget is realized before connecting it, cause it might not be on the screen and video_area.window will be None === ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - http://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi [21:00] then we just set the xwindow id [21:01] and we're done [21:01] [SLIDE 17] [21:01] heres how to seek from the value on the slider [21:01] [SLIDE 18] [21:02] if there any questions about that, ask me [21:02] [SLIDE 19] [21:02] QUESTION: How do I seek into a movie and display a still thumbnail? [21:02] thanks for reminding me [21:03] you should keep the state in PAUSED so that the first frame shows [21:03] and then seek with FLUSH and KEY [21:03] this is important because if it is plaused, there is data in the pipeline thats not moving [21:04] FLUSH will tell the elements to throw out the old data and only use the data from the new position [21:04] KEY_UNIT will tell the decoder to seek to a key frame, so you don't get a partially decoded blocky frame from the video [21:04] QUESTION: what do that seek flags mean? [21:05] is it clear from what i said on slide 18? [21:05] [SLIDE 18] [21:05] [SLIDE 19] [21:06] if you have not seen gobject.timeout_add() before, it will call your function at the time interval given as long as the mainloop is still running [21:06] 100 means 100 milliseconds, or 10 times per second [21:07] if you have some other intensive operation which is blocking the mainloop it will be called much less often and your GUI will be unresponsive [21:07] so every 100ms we query the position and update the slider, make sense? [21:07] [SLIDE 20] [21:08] here I am querying both the position and duration, because sometimes the duration changes [21:08] for many audio types like mp3, the duration is an estimate [21:09] there is no way to know the exact length without playing the file to the end and saving the result [21:09] QUESTION: Why would the duration change? for streaming files? [21:09] often with streaming files you don't know how long it is, the duration query will fail [21:10] other files like mp3 i mentioned the duration is estimated by calculating it form the size of the file and the bitrate [21:10] if the file is a poorly encoded variable bitrate file, the duration estimate may change [21:11] gstreamer requires you specify the format you want [21:11] but almost always we want TIME (which comes back in nanoseconds) [21:11] you can call query bytes or percent for example [21:11] [SLIDE 21] [21:12] so once we have the values from the query here is how we update the slider [21:12] make sure you block the on_slider_change function or that will be called with you do set_value() [21:12] [SLIDE 22] [21:12] http://laszlopandy.com/files/playback_interface_complete.html [21:12] heres the completed code [21:13] I added a little thing there to always read the file from the current directory [21:13] 120 lines and you can do the basics of what totem does [21:14] thanks guys, i'm just gonna answer questions now [21:14] QUESTION: possible to extract/record a slice of audio/video from a file to a file ? faster than real-time ? (yes/no answer is enough) [21:14] yes it is possible [21:15] if you don't have an element which requires real time (like going from files to files) gstreamer will go as fast as possible [21:15] to extract slices of audio/video files the elements you want are in the gnonlin package [21:15] they are used by pitivi for example [21:16] QUESTION: is it possible to seek to/display a still of a non-keyframe? [21:16] yes, i believe this is what happens when you leave out the KEY_UNIT [21:16] it will take longer to seek, because the decoder has to find a key frame and decode everything after that to get the full frame [21:17] but this is what totem does for its frame-by-frame step feature [21:17] QUESTION: Can you help me to find "wmap" codec for .wmv files? [21:17] find me later in #jokosher and i'll see [21:18] QUESTION: If I have a video in HD and only want to display it as a thumbnail, is this simple, and does gstreamer do clever stuff to provide low memory usage? [21:18] gstreamer is fairly good with memory usage [21:19] i believe the nautilus thumbnailer which gets those pictures of your videos uses gstreamer and just sets it to paused to extract a single frame [21:19] sometimes it does use a lot of IO though [21:20] okay so if the movie is playing, and you want a smaller copy of it [21:20] this is just the same as when you scale down the totem window [21:21] there is an element called tee to make copies of streams [21:21] so you can have one small copy and one large playing at the same time even [21:21] but for an HD video it sill have to decode the entire thing, then scale it down which will require a lot of cpu [21:22] however the gstreamer magicians have some more tricks which are currently only prototype and use cairo to draw the scaled video directly instead of copying the HD video around [21:22] you may have seen it on the planet, it sounds pretty much like what mattmole is looking for