fdaa7c1d17e4df8ab6b00bb99d56b0a1fda8b505
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

1) #! /usr/bin/env python
2) 
Stefan Schuermans add copyright, remove statu...

Stefan Schuermans authored 10 years ago

3) # BlinkenArea Sync GUI
4) # version 0.1.0 date 2013-11-23
5) # Copyright 2013 Stefan Schuermans <stefan@blinkenarea.org>
6) # Copyleft: GNU public license - http://www.gnu.org/copyleft/gpl.html
7) # a blinkenarea.org project - https://www.blinkenarea.org/
8) 
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

9) import os
10) from gi.repository import Gtk
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

11) import gobject
Stefan Schuermans show current playlist item...

Stefan Schuermans authored 10 years ago

12) import pango
Stefan Schuermans implement UDP sync protocol...

Stefan Schuermans authored 10 years ago

13) import socket
14) import struct
Stefan Schuermans add menu, implement file open

Stefan Schuermans authored 10 years ago

15) import sys
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

16) import time
Stefan Schuermans reading playlist

Stefan Schuermans authored 10 years ago

17) import playlist
Stefan Schuermans show current position as h:...

Stefan Schuermans authored 10 years ago

18) import time_fmt
19) 
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

20) scriptdir = os.path.dirname(os.path.abspath(__file__))
21) 
22) class SyncGui:
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

23) 
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

24)   def __init__(self):
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

25)     """construct a SyncGui object"""
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

26)     self.builder = Gtk.Builder()
27)     self.builder.add_from_file(scriptdir + "/sync_gui.glade")
Stefan Schuermans add menu, implement file open

Stefan Schuermans authored 10 years ago

28)     self.widMainWindow = self.builder.get_object("MainWindow")
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

29)     self.widPlaylistView = self.builder.get_object("PlaylistView")
30)     self.widPlaylistStore = self.builder.get_object("PlaylistStore")
31)     self.widPosition = self.builder.get_object("Position")
32)     self.widPositionScale = self.builder.get_object("PositionScale")
33)     self.widPositionAt = self.builder.get_object("PositionAt")
34)     self.widPositionRemaining = self.builder.get_object("PositionRemaining")
35)     self.widBtnPause = self.builder.get_object("Pause")
36)     self.widBtnPlay = self.builder.get_object("Play")
Stefan Schuermans use second bulb to display...

Stefan Schuermans authored 10 years ago

37)     self.widLogoStop = self.builder.get_object("LogoStop")
38)     self.widLogoPlay = self.builder.get_object("LogoPlay")
39)     self.widLogoUdpErr = self.builder.get_object("LogoUdpErr")
40)     self.widLogoUdpOk = self.builder.get_object("LogoUdpOk")
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

41)     self.widStatus = self.builder.get_object("Status")
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

42)     handlers = {
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

43)       "onDestroy":           self.onDestroy,
44)       "onFileOpen":          self.onFileOpen,
45)       "onFileExit":          self.onFileExit,
46)       "onExtrasDestination": self.onExtrasDestination,
47)       "onPlaylistDblClick":  self.onPlaylistDblClick,
48)       "onNewPosition":       self.onNewPosition,
49)       "onPrevious":          self.onPrevious,
50)       "onStop":              self.onStop,
51)       "onPause":             self.onPause,
52)       "onPlay":              self.onPlay,
53)       "onNext":              self.onNext,
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

54)     }
55)     self.builder.connect_signals(handlers)
Stefan Schuermans reading playlist

Stefan Schuermans authored 10 years ago

56)     self.playlist = playlist.Playlist()
Stefan Schuermans add menu, implement file open

Stefan Schuermans authored 10 years ago

57)     if len(sys.argv) >= 2: # load initial playlist from command line
58)       self.playlist.read(sys.argv[1])
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

59)     self.playlist.update(self.widPlaylistStore)
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

60)     self.sock = None
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

61)     self.stEntryIdx = -1 # no entry selected
62)     self.stName = "" # no current entry name
63)     self.stDuration = 0 # current entry has zero size
64)     self.stPosition = 0 # at begin of current entry
65)     self.stPlaying = False # not playing
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

66)     self.stDestination = "255.255.255.255" # local LAN broadcast by default
67)     self.setupSock()
Stefan Schuermans show current playlist item...

Stefan Schuermans authored 10 years ago

68)     self.updateEntry()
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

69)     self.updateButtonVisibility()
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

70)     gobject.timeout_add(10, self.onTimer10ms)
71)     gobject.timeout_add(100, self.onTimer100ms)
Stefan Schuermans show current position as h:...

Stefan Schuermans authored 10 years ago

72) 
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

73)   def showPosition(self):
74)     """update the position texts next to the position slider"""
75)     # format current time and remaining time
76)     posAt = time_fmt.sec2str(self.stPosition)
77)     posRemaining = time_fmt.sec2str(self.stDuration - self.stPosition)
78)     self.widPositionAt.set_text(posAt)
79)     self.widPositionRemaining.set_text(posRemaining)
80) 
81)   def updatePositionState(self):
82)     """update the position in the state, but not the slider"""
83)     # calculate (virtual) start time of playing
84)     # i.e. the time the playing would have had started to arrive at the
85)     # current position now if it had played continuosly
86)     self.stPlayStart = time.time() - self.stPosition
87)     # update position texts
88)     self.showPosition()
89) 
90)   def updatePosition(self):
91)     """update the position including the position slider"""
92)     # update GUI slider
93)     self.widPositionScale.set_value(self.stPosition)
94)     # update position state
95)     self.updatePositionState()
96) 
97)   def updateDuration(self):
98)     """update the duration (i.e. range for the slider) based on the current
99)        playlist entry"""
100)     # get duration of new playlist entry
101)     self.stDuration = 0
102)     if self.stEntryIdx >= 0:
103)       entry = self.playlist.entries[self.stEntryIdx]
104)       if entry["type"] == "normal":
105)         self.stDuration = entry["duration"]
106)     # set position to begin
107)     self.stPosition = 0
108)     # update value range
109)     self.widPosition.set_upper(self.stDuration)
110)     # update position of slider
111)     self.updatePosition()
112) 
Stefan Schuermans show current playlist item...

Stefan Schuermans authored 10 years ago

113)   def updateEntry(self):
114)     """update current entry of playlist and duration, position, ..."""
Stefan Schuermans remove forward and backward...

Stefan Schuermans authored 10 years ago

115)     # clear selection of playlist
116)     sel = self.widPlaylistView.get_selection()
117)     if sel:
118)       sel.unselect_all()
Stefan Schuermans show current playlist item...

Stefan Schuermans authored 10 years ago

119)     # sanity check for entry index
120)     if self.stEntryIdx < -1 or self.stEntryIdx >= len(self.playlist.entries):
121)       self.stEntryIdx = -1
Stefan Schuermans add menu, implement file open

Stefan Schuermans authored 10 years ago

122)     # get name of current entry
123)     self.stName = ""
124)     if self.stEntryIdx >= 0 and \
125)        self.playlist.entries[self.stEntryIdx]["type"] == "normal":
126)       self.stName = self.playlist.entries[self.stEntryIdx]["name"]
Stefan Schuermans show current playlist item...

Stefan Schuermans authored 10 years ago

127)     # make current entry bold, all others non-bold
128)     def update(model, path, it, user_data):
129)       (idx,) = model.get(it, 0)
130)       if idx == self.stEntryIdx:
131)         weight = pango.WEIGHT_BOLD
132)       else:
133)         weight = pango.WEIGHT_NORMAL
134)       model.set(it, 1, weight)
135)     self.widPlaylistStore.foreach(update, None)
Stefan Schuermans remove forward and backward...

Stefan Schuermans authored 10 years ago

136)     # playing and (no entry or stop entry)
137)     # -> stop playing and update button visibility
138)     if self.stPlaying and (self.stEntryIdx < 0 or \
139)        self.playlist.entries[self.stEntryIdx]["type"] == "stop"):
140)       self.stPlaying = False
141)       self.updateButtonVisibility()
Stefan Schuermans show current playlist item...

Stefan Schuermans authored 10 years ago

142)     # update duration, position, ...
143)     self.updateDuration()
144) 
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

145)   def updateButtonVisibility(self):
146)     """update the visibility of the buttons based on if playing or not"""
147)     self.widBtnPause.set_visible(self.stPlaying)
148)     self.widBtnPlay.set_visible(not self.stPlaying)
Stefan Schuermans use second bulb to display...

Stefan Schuermans authored 10 years ago

149)     self.widLogoStop.set_visible(not self.stPlaying)
150)     self.widLogoPlay.set_visible(self.stPlaying)
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

151) 
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

152)   def closeSock(self):
153)     """close UDP socket"""
Stefan Schuermans use second bulb to display...

Stefan Schuermans authored 10 years ago

154)     self.widLogoUdpErr.set_visible(True)
155)     self.widLogoUdpOk.set_visible(False)
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

156)     self.widStatus.remove_all(0)
157)     self.widStatus.push(0, "UDP output ERROR")
158)     if self.sock is not None:
159)       self.sock.close()
160)     self.sock = None
161) 
162)   def setupSock(self):
163)     """create a new UDP socket and "connect" it to the destination address"""
164)     self.closeSock()
165)     try:
166)       self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
167)       self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
168)       self.sock.connect((self.stDestination, 5740))
169)       self.widStatus.remove_all(0)
170)       self.widStatus.push(0, "UDP output to \"" + self.stDestination +
171)                              "\" port 5740")
Stefan Schuermans use second bulb to display...

Stefan Schuermans authored 10 years ago

172)       self.widLogoUdpErr.set_visible(False)
173)       self.widLogoUdpOk.set_visible(True)
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

174)     except:
175)       self.closeSock()
176) 
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

177)   def onDestroy(self, widget):
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

178)     """window will be destroyed"""
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

179)     Gtk.main_quit()
180) 
Stefan Schuermans add menu, implement file open

Stefan Schuermans authored 10 years ago

181)   def onFileOpen(self, widget):
182)     """File Open clicked in menu"""
183)     #print("DEBUG sync_gui File Open")
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

184)     # create and run file chooser dialog
185)     dialog = Gtk.FileChooserDialog(
186)       "BlinkenArea Sync GUI - File Open...",
187)       self.widMainWindow, Gtk.FileChooserAction.OPEN,
188)       (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
189)        Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
Stefan Schuermans add menu, implement file open

Stefan Schuermans authored 10 years ago

190)     dialog.set_default_response(Gtk.ResponseType.OK)
191)     filt = Gtk.FileFilter()
192)     filt.set_name("All files")
193)     filt.add_pattern("*")
194)     dialog.add_filter(filt)
195)     response = dialog.run()
196)     if response == Gtk.ResponseType.OK:
197)       # dialog closed with OK -> load new playlist
198)       filename = dialog.get_filename()
199)       self.playlist.read(filename)
200)       self.playlist.update(self.widPlaylistStore)
201)       self.stEntryIdx = -1 # no entry selected
202)       self.stPlaying = False # not playing
203)       self.updateEntry()
204)       self.updateButtonVisibility()
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

205)     # clean up
Stefan Schuermans add menu, implement file open

Stefan Schuermans authored 10 years ago

206)     dialog.destroy()
207) 
208)   def onFileExit(self, widget):
209)     """File Exit clicked in menu"""
210)     #print("DEBUG sync_gui File Exit")
211)     Gtk.main_quit()
212) 
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

213)   def onExtrasDestination(self, widget):
214)     """Extras Destination Address clicked in menu"""
215)     #print("DEBUG sync_gui Extras Destination")
216)     # run input dialog to ask for new destination
217)     dialog = self.builder.get_object("DialogDestination")
218)     cur = self.builder.get_object("DiaDestCur")
219)     new = self.builder.get_object("DiaDestNew")
220)     cur.set_text(self.stDestination)
221)     new.set_text(self.stDestination)
222)     response = dialog.run()
223)     if response == 1:
224)       self.stDestination = new.get_text()
225)     # hide input dialog
226)     dialog.hide()
227)     # re-create UDP socket
228)     self.setupSock()
229) 
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

230)   def onPlaylistDblClick(self, widget, row, col):
231)     """playlist entry has been double-clicked"""
232)     # get index of selected entry
233)     idx = -1
234)     sel = self.widPlaylistView.get_selection()
235)     if sel is not None:
236)       (model, it) = sel.get_selected()
237)       if it is not None:
Stefan Schuermans show current playlist item...

Stefan Schuermans authored 10 years ago

238)         (idx,) = model.get(it, 0)
Stefan Schuermans remove forward and backward...

Stefan Schuermans authored 10 years ago

239)     #print("DEBUG sync_gui playlist double-click idx=%d" % (idx))
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

240)     # update playlist entry
241)     self.stEntryIdx = idx
Stefan Schuermans remove forward and backward...

Stefan Schuermans authored 10 years ago

242)     # set position to zero if playing
243)     if self.stPlaying:
244)       self.stPosition = 0
Stefan Schuermans show current playlist item...

Stefan Schuermans authored 10 years ago

245)     # update entry
246)     self.updateEntry()
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

247) 
Stefan Schuermans show current position as h:...

Stefan Schuermans authored 10 years ago

248)   def onNewPosition(self, widget, scroll, value):
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

249)     """slider has been moved to a new position"""
Stefan Schuermans remove forward and backward...

Stefan Schuermans authored 10 years ago

250)     #print("DEBUG sync_gui new position " + str(value));
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

251)     # clamp position to valid range
252)     if value < 0:
253)       value = 0
254)     if value > self.stDuration:
255)       value = self.stDuration
256)     # update current position - and play start time if playing
257)     self.stPosition = value
258)     # update position state (do not touch the slider)
259)     self.updatePositionState()
Stefan Schuermans show current position as h:...

Stefan Schuermans authored 10 years ago

260) 
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

261)   def onPrevious(self, widget):
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

262)     """previous button as been pressed"""
Stefan Schuermans remove forward and backward...

Stefan Schuermans authored 10 years ago

263)     #print("DEBUG sync_gui previous")
264)     # go to begin of previous entry (with wrap around)
265)     self.stPosition = 0
266)     self.stEntryIdx = self.stEntryIdx - 1
267)     if self.stEntryIdx < 0:
268)       self.stEntryIdx = len(self.playlist.entries) - 1
269)     self.updateEntry()
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

270) 
271)   def onStop(self, widget):
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

272)     """stop button has been pressed"""
Stefan Schuermans remove forward and backward...

Stefan Schuermans authored 10 years ago

273)     #print("DEBUG sync_gui stop")
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

274)     self.stPlaying = False
275)     self.stPosition = 0 # stop goes back to begin
276)     self.updatePosition()
277)     self.updateButtonVisibility()
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

278) 
279)   def onPause(self, widget):
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

280)     """pause button has been pressed"""
Stefan Schuermans remove forward and backward...

Stefan Schuermans authored 10 years ago

281)     #print("DEBUG sync_gui pause")
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

282)     self.stPlaying = False
283)     self.updateButtonVisibility()
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

284) 
285)   def onPlay(self, widget):
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

286)     """play button has been pressed"""
Stefan Schuermans remove forward and backward...

Stefan Schuermans authored 10 years ago

287)     #print("DEBUG sync_gui play")
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

288)     self.stPlaying = True
289)     self.updatePosition()
290)     self.updateButtonVisibility()
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

291) 
292)   def onNext(self, widget):
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

293)     """next button has been pressed"""
Stefan Schuermans remove forward and backward...

Stefan Schuermans authored 10 years ago

294)     #print("DEBUG sync_gui next")
295)     # go to begin of next entry (with wrap around)
296)     self.stPosition = 0
297)     self.stEntryIdx = self.stEntryIdx + 1
298)     if self.stEntryIdx >= len(self.playlist.entries):
299)       self.stEntryIdx = 0
300)     self.updateEntry()
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

301) 
302)   def onTimer10ms(self):
303)     """timer callback, every 10ms"""
304)     # update position if playing
305)     if self.stPlaying:
306)       self.stPosition = time.time() - self.stPlayStart
Stefan Schuermans remove forward and backward...

Stefan Schuermans authored 10 years ago

307)       if self.stPosition >= self.stDuration:
308)         # end of entry reached --> go to begin of next entry (with wrap around)
309)         self.stPosition = 0
310)         self.stEntryIdx = self.stEntryIdx + 1
311)         if self.stEntryIdx >= len(self.playlist.entries):
312)           self.stEntryIdx = 0
313)         self.updateEntry()
314)       else:
315)         self.updatePosition()
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

316)     # request being called again
317)     return True
Stefan Schuermans implement UDP sync protocol...

Stefan Schuermans authored 10 years ago

318) 
319)   def onTimer100ms(self):
320)     """timer callback, every 100ms"""
321)     # send sync packet
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

322)     if self.sock is not None:
323)       flags = 0
324)       if self.stPlaying:
325)         flags = flags | 1
326)       name = self.stName
327)       pos_ms = round(self.stPosition * 1000)
328)       data = "PoSy" + struct.pack("!I64sI", flags, name, pos_ms)
329)       try:
330)         self.sock.send(data)
331)       except:
332)         self.closeSock()
333)     # request being called again
334)     return True
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

335) 
336) # main application entry point
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

337) if __name__ == "__main__":
338)   app = SyncGui()
339)   Gtk.main()
340)