8f3c324f5e75db4b72f7c961486265fdf1c1fd60
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 11 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 11 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 11 years ago

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

Stefan Schuermans authored 11 years ago

18) import time_fmt
19) 
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 11 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 11 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 11 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 make logo change color when...

Stefan Schuermans authored 10 years ago

37)     self.widLogoO = self.builder.get_object("LogoO")
38)     self.widLogoG = self.builder.get_object("LogoG")
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 11 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 11 years ago

52)     }
53)     self.builder.connect_signals(handlers)
Stefan Schuermans reading playlist

Stefan Schuermans authored 11 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 11 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

143)   def updateButtonVisibility(self):
144)     """update the visibility of the buttons based on if playing or not"""
145)     self.widBtnPause.set_visible(self.stPlaying)
146)     self.widBtnPlay.set_visible(not self.stPlaying)
Stefan Schuermans make logo change color when...

Stefan Schuermans authored 10 years ago

147)     self.widLogoO.set_visible(not self.stPlaying)
148)     self.widLogoG.set_visible(self.stPlaying)
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 11 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 11 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 11 years ago

173)     Gtk.main_quit()
174) 
Stefan Schuermans add menu, implement file open

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

178)     # create and run file chooser dialog
179)     dialog = Gtk.FileChooserDialog(
180)       "BlinkenArea Sync GUI - File Open...",
181)       self.widMainWindow, Gtk.FileChooserAction.OPEN,
182)       (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
183)        Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
Stefan Schuermans add menu, implement file open

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

200)     dialog.destroy()
201) 
202)   def onFileExit(self, widget):
203)     """File Exit clicked in menu"""
204)     #print("DEBUG sync_gui File Exit")
205)     Gtk.main_quit()
206) 
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

224)   def onPlaylistDblClick(self, widget, row, col):
225)     """playlist entry has been double-clicked"""
226)     # get index of selected entry
227)     idx = -1
228)     sel = self.widPlaylistView.get_selection()
229)     if sel is not None:
230)       (model, it) = sel.get_selected()
231)       if it is not None:
Stefan Schuermans show current playlist item...

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

236)     # set position to zero if playing
237)     if self.stPlaying:
238)       self.stPosition = 0
Stefan Schuermans show current playlist item...

Stefan Schuermans authored 10 years ago

239)     # update entry
240)     self.updateEntry()
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 11 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

245)     # clamp position to valid range
246)     if value < 0:
247)       value = 0
248)     if value > self.stDuration:
249)       value = self.stDuration
250)     # update current position - and play start time if playing
251)     self.stPosition = value
252)     # update position state (do not touch the slider)
253)     self.updatePositionState()
Stefan Schuermans show current position as h:...

Stefan Schuermans authored 11 years ago

254) 
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 11 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

257)     #print("DEBUG sync_gui previous")
258)     # go to begin of previous entry (with wrap around)
259)     self.stPosition = 0
260)     self.stEntryIdx = self.stEntryIdx - 1
261)     if self.stEntryIdx < 0:
262)       self.stEntryIdx = len(self.playlist.entries) - 1
263)     self.updateEntry()
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 11 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

268)     self.stPlaying = False
269)     self.stPosition = 0 # stop goes back to begin
270)     self.updatePosition()
271)     self.updateButtonVisibility()
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 11 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

276)     self.stPlaying = False
277)     self.updateButtonVisibility()
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 11 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

282)     self.stPlaying = True
283)     self.updatePosition()
284)     self.updateButtonVisibility()
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 11 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

288)     #print("DEBUG sync_gui next")
289)     # go to begin of next entry (with wrap around)
290)     self.stPosition = 0
291)     self.stEntryIdx = self.stEntryIdx + 1
292)     if self.stEntryIdx >= len(self.playlist.entries):
293)       self.stEntryIdx = 0
294)     self.updateEntry()
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

295) 
296)   def onTimer10ms(self):
297)     """timer callback, every 10ms"""
298)     # update position if playing
299)     if self.stPlaying:
300)       self.stPosition = time.time() - self.stPlayStart
Stefan Schuermans remove forward and backward...

Stefan Schuermans authored 10 years ago

301)       if self.stPosition >= self.stDuration:
302)         # end of entry reached --> go to begin of next entry (with wrap around)
303)         self.stPosition = 0
304)         self.stEntryIdx = self.stEntryIdx + 1
305)         if self.stEntryIdx >= len(self.playlist.entries):
306)           self.stEntryIdx = 0
307)         self.updateEntry()
308)       else:
309)         self.updatePosition()
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

310)     # request being called again
311)     return True
Stefan Schuermans implement UDP sync protocol...

Stefan Schuermans authored 10 years ago

312) 
313)   def onTimer100ms(self):
314)     """timer callback, every 100ms"""
315)     # send sync packet
Stefan Schuermans implement UDP output and de...

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

329) 
330) # main application entry point
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 11 years ago

331) if __name__ == "__main__":
332)   app = SyncGui()
333)   Gtk.main()
334)