9c1981a0bad501e086573259bc6fb4e209052821
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 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 initial PyGtk window

Stefan Schuermans authored 10 years ago

39)     handlers = {
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

40)       "onDestroy":          self.onDestroy,
Stefan Schuermans add menu, implement file open

Stefan Schuermans authored 10 years ago

41)       "onFileOpen":         self.onFileOpen,
42)       "onFileExit":         self.onFileExit,
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

43)       "onPlaylistDblClick": self.onPlaylistDblClick,
44)       "onNewPosition":      self.onNewPosition,
45)       "onPrevious":         self.onPrevious,
46)       "onStop":             self.onStop,
47)       "onPause":            self.onPause,
48)       "onPlay":             self.onPlay,
49)       "onNext":             self.onNext,
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

50)     }
51)     self.builder.connect_signals(handlers)
Stefan Schuermans reading playlist

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

55)     self.playlist.update(self.widPlaylistStore)
56)     self.stEntryIdx = -1 # no entry selected
57)     self.stName = "" # no current entry name
58)     self.stDuration = 0 # current entry has zero size
59)     self.stPosition = 0 # at begin of current entry
60)     self.stPlaying = False # not playing
Stefan Schuermans implement UDP sync protocol...

Stefan Schuermans authored 10 years ago

61)     self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
62)     self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
63)     self.sock.connect(("255.255.255.255", 5740))
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

64)     gobject.timeout_add(10, self.onTimer10ms)
Stefan Schuermans implement UDP sync protocol...

Stefan Schuermans authored 10 years ago

65)     gobject.timeout_add(100, self.onTimer100ms)
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 show current position as h:...

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

145)     self.widLogoO.set_visible(not self.stPlaying)
146)     self.widLogoG.set_visible(self.stPlaying)
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

150)     Gtk.main_quit()
151) 
Stefan Schuermans add menu, implement file open

Stefan Schuermans authored 10 years ago

152)   def onFileOpen(self, widget):
153)     """File Open clicked in menu"""
154)     #print("DEBUG sync_gui File Open")
155)     # run file chooser dialog
156)     dialog = Gtk.FileChooserDialog("BlinkenArea Sync GUI - File Open..",
157)                                    self.widMainWindow,
158)                                    Gtk.FileChooserAction.OPEN,
159)                                    (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
160)                                     Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
161)     dialog.set_default_response(Gtk.ResponseType.OK)
162)     filt = Gtk.FileFilter()
163)     filt.set_name("All files")
164)     filt.add_pattern("*")
165)     dialog.add_filter(filt)
166)     response = dialog.run()
167)     if response == Gtk.ResponseType.OK:
168)       # dialog closed with OK -> load new playlist
169)       filename = dialog.get_filename()
170)       self.playlist.read(filename)
171)       self.playlist.update(self.widPlaylistStore)
172)       self.stEntryIdx = -1 # no entry selected
173)       self.stPlaying = False # not playing
174)       self.updateEntry()
175)       self.updateButtonVisibility()
176)     # cleanup
177)     dialog.destroy()
178) 
179)   def onFileExit(self, widget):
180)     """File Exit clicked in menu"""
181)     #print("DEBUG sync_gui File Exit")
182)     Gtk.main_quit()
183) 
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

184)   def onPlaylistDblClick(self, widget, row, col):
185)     """playlist entry has been double-clicked"""
186)     # get index of selected entry
187)     idx = -1
188)     sel = self.widPlaylistView.get_selection()
189)     if sel is not None:
190)       (model, it) = sel.get_selected()
191)       if it is not None:
Stefan Schuermans show current playlist item...

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

196)     # set position to zero if playing
197)     if self.stPlaying:
198)       self.stPosition = 0
Stefan Schuermans show current playlist item...

Stefan Schuermans authored 10 years ago

199)     # update entry
200)     self.updateEntry()
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

205)     # clamp position to valid range
206)     if value < 0:
207)       value = 0
208)     if value > self.stDuration:
209)       value = self.stDuration
210)     # update current position - and play start time if playing
211)     self.stPosition = value
212)     # update position state (do not touch the slider)
213)     self.updatePositionState()
Stefan Schuermans show current position as h:...

Stefan Schuermans authored 10 years ago

214) 
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

217)     #print("DEBUG sync_gui previous")
218)     # go to begin of previous entry (with wrap around)
219)     self.stPosition = 0
220)     self.stEntryIdx = self.stEntryIdx - 1
221)     if self.stEntryIdx < 0:
222)       self.stEntryIdx = len(self.playlist.entries) - 1
223)     self.updateEntry()
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

228)     self.stPlaying = False
229)     self.stPosition = 0 # stop goes back to begin
230)     self.updatePosition()
231)     self.updateButtonVisibility()
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

236)     self.stPlaying = False
237)     self.updateButtonVisibility()
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

242)     self.stPlaying = True
243)     self.updatePosition()
244)     self.updateButtonVisibility()
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

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

Stefan Schuermans authored 10 years ago

248)     #print("DEBUG sync_gui next")
249)     # go to begin of next entry (with wrap around)
250)     self.stPosition = 0
251)     self.stEntryIdx = self.stEntryIdx + 1
252)     if self.stEntryIdx >= len(self.playlist.entries):
253)       self.stEntryIdx = 0
254)     self.updateEntry()
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

255) 
256)   def onTimer10ms(self):
257)     """timer callback, every 10ms"""
258)     # update position if playing
259)     if self.stPlaying:
260)       self.stPosition = time.time() - self.stPlayStart
Stefan Schuermans remove forward and backward...

Stefan Schuermans authored 10 years ago

261)       if self.stPosition >= self.stDuration:
262)         # end of entry reached --> go to begin of next entry (with wrap around)
263)         self.stPosition = 0
264)         self.stEntryIdx = self.stEntryIdx + 1
265)         if self.stEntryIdx >= len(self.playlist.entries):
266)           self.stEntryIdx = 0
267)         self.updateEntry()
268)       else:
269)         self.updatePosition()
Stefan Schuermans implement UDP sync protocol...

Stefan Schuermans authored 10 years ago

270)     return True # call again
271) 
272)   def onTimer100ms(self):
273)     """timer callback, every 100ms"""
274)     # send sync packet
275)     flags = 0
276)     if self.stPlaying:
277)       flags = flags | 1
278)     name = self.stName
279)     pos_ms = round(self.stPosition * 1000)
280)     data = "PoSy" + struct.pack("!I64sI", flags, name, pos_ms)
281)     self.sock.send(data)
282)     return True # call again
Stefan Schuermans start making player work -...

Stefan Schuermans authored 10 years ago

283) 
284) # main application entry point
Stefan Schuermans initial PyGtk window

Stefan Schuermans authored 10 years ago

285) if __name__ == "__main__":
286)   app = SyncGui()
287)   Gtk.main()
288)