6f578990e8c8e90881194cc522a97635c5473577
Stefan Schuermans script to synchronize mplay...

Stefan Schuermans authored 10 years ago

1) #! /usr/bin/env python
2) 
3) # MPlayer synchronizer
4) # Copyright 2014 Stefan Schuermans <stefan@schuermans.info>
5) # Copyleft: GNU public license - http://www.gnu.org/copyleft/gpl.html
6) 
7) import datetime
8) import fcntl
9) import os
10) import re
11) import select
12) import socket
13) import struct
14) import subprocess
15) import sys
16) import time
17) 
18) scriptdir = os.path.dirname(os.path.abspath(__file__))
19) 
20) verbose = False
21) 
22) class Synchronizer:
23) 
24)   def __init__(self):
25)     """construct an MPlayer Synchronizer object"""
26)     # set constants
27)     self.info_timeout     = 1.0   # timeout (s) for info from MPlayer or PoSy
28)     self.max_equal_offset = 0.1   # maximum offset tolerated as equal
29)     self.min_cmd_delay    = 0.1   # minimum time (s) between MPlayer commands
30)     self.min_seek_offset  = 0.5   # minimum offset (s) required for a seek
31)     self.select_timeout   = 0.1   # timeout (s) for select syscall
32)     self.speed_change     = 0.05  # change of MPlayer speed for catching up
33)     # create static objects
34)     self.re_mplayer_pos = re.compile(r"[AV]: *([0-9]+.[0-9]+) *\([0-9:.]*\) of .*")
Stefan Schuermans change local file name pref...

Stefan Schuermans authored 10 years ago

35)     self.re_ignore_prefix = re.compile(r"[0-9a-zA-Z_]+__(.*)")
Stefan Schuermans script to synchronize mplay...

Stefan Schuermans authored 10 years ago

36)     # create member variables
37)     self.mplayer = None
38)     self.mplayer_buf_stdout = ""
39)     self.mplayer_buf_stderr = ""
40)     self.mplayer_last_cmd_timestamp = None
41)     self.mplayer_name = None
42)     self.mplayer_pause = None
43)     self.mplayer_pos = None
44)     self.mplayer_speed = None
45)     self.mplayer_timestamp = None
46)     self.offset_samples = []
47)     self.playlist = []
48)     self.playlist_idx = None
49)     self.posy_name = None
50)     self.posy_pause = None
51)     self.posy_pos = None
52)     self.posy_timestamp = None
53)     self.sock = None
54)     self.verbose = False
55)     # startup
56)     self.sockSetup()
57) 
58)   def __del__(self):
59)     """deconstruct object"""
60)     self.mplayerStop()
61)     self.sockClose()
62) 
63)   def dbg_print(self, txt):
64)     """output debug information in verbose mode"""
65)     if self.verbose:
66)       print >>sys.stderr, txt
67) 
68)   def mplayerClear(self):
69)     """clear MPlayer buffers and information"""
70)     self.mplayer_buf_stdin = ""
71)     self.mplayer_buf_stderr = ""
72)     self.mplayer_last_cmd_timestamp = None
73)     self.mplayer_name = None
74)     self.mplayer_pause = None
75)     self.mplayer_pos = None
76)     self.mplayer_speed = None
77)     self.mplayer_timestamp = None
78)     self.offset_samples = []
79) 
80)   def mplayerExit(self):
81)     """react to MPlayer exit"""
82)     # close pipes
83)     self.mplayer.stdin.close()
84)     self.mplayer.stdout.close()
85)     self.mplayer.stderr.close()
86)     # close process
87)     self.mplayer.wait()
88)     self.mplayer = None
89)     # clear buffers and information
90)     self.mplayerClear()
91)     # play next file
92)     self.playNext()
93) 
94)   def mplayerLine(self, line, err):
95)     """process line from MPlayer stdout (err = False) or stderr (err = True)"""
96)     if len(line) > 0:
97)       if err:
98)         self.dbg_print("MPlayer stderr: " + line)
99)       else:
100)         self.dbg_print("MPlayer stdout: " + line)
101)     if not err:
102)       # MPlayer position information
103)       m_mplayer_pos = self.re_mplayer_pos.match(line)
104)       if m_mplayer_pos:
105)         self.mplayer_timestamp = datetime.datetime.now()
106)         self.mplayer_pos = float(m_mplayer_pos.group(1))
107)         # synchronize
108)         self.sync()
109) 
110)   def mplayerPause(self, pause):
111)     """pause/unpause MPlayer"""
112)     # leave if no MPlayer running
113)     if self.mplayer is None:
114)       return
115)     # switch to pause mode
116)     if pause:
117)       self.dbg_print("MPlayer stdin: pausing seek 0 0")
118)       self.mplayer.stdin.write("pausing seek 0 0\n") # rel seek 0s, then pause
119)       self.mplayer_pause = True
120)     # continue playing
121)     else:
122)       self.dbg_print("MPlayer stdin: seek 0 0")
123)       self.mplayer.stdin.write("seek 0 0\n") # realtive seek of 0s
124)       self.mplayer_pause = False
125)     self.mplayer_last_cmd_timestamp = datetime.datetime.now()
126) 
127)   def mplayerSetPos(self, pos):
128)     """set MPlayer position"""
129)     # leave if no MPlayer running
130)     if self.mplayer is None:
131)       return
132)     # sanitize position to avoid mplayer crashes in any case
133)     if pos < 0.0:
134)       pos = 0.0
135)     # set new position
136)     self.dbg_print("MPlayer stdin: seek %5.3f 2" % pos)
137)     self.mplayer.stdin.write("seek %5.3f 2\n" % pos) # 2 means absolute pos
138)     self.mplayer_pos = pos
139)     self.mplayer_last_cmd_timestamp = datetime.datetime.now()
140) 
141)   def mplayerSetSpeed(self, speed):
142)     """set MPlayer speed"""
143)     # leave if no MPlayer running
144)     if self.mplayer is None:
145)       return
146)     # sanitize speed to avoid mplayer crashes in any case
147)     if speed < 0.5:
148)       speed = 0.5
149)     if speed > 2.0:
150)       speed = 2.0
151)     # set new speed
152)     self.dbg_print("MPlayer stdin: speed_set %5.3f" % speed)
153)     self.mplayer.stdin.write("speed_set %5.3f\n" % speed)
154)     self.mplayer_speed = speed
155)     self.mplayer_last_cmd_timestamp = datetime.datetime.now()
156) 
157)   def mplayerStart(self, filename):
158)     """start MPlayer process in background"""
159)     # stop old MPlayer
160)     self.mplayerStop()
161)     # start MPlayer
Stefan Schuermans start mplayer with higher v...

Stefan Schuermans authored 10 years ago

162)     cmd = [ "mplayer", "-volume", "80", "-slave", "-af", "scaletempo",
163)             filename ]
Stefan Schuermans script to synchronize mplay...

Stefan Schuermans authored 10 years ago

164)     print >>sys.stderr, "starting background process: " + " ".join(cmd)
165)     self.mplayer = subprocess.Popen(cmd, stdin = subprocess.PIPE,
166)                                          stdout = subprocess.PIPE,
167)                                          stderr = subprocess.PIPE)
168)     # make output pipes nonblocking
169)     fcntl.fcntl(self.mplayer.stdout, fcntl.F_SETFL, os.O_NONBLOCK)
170)     fcntl.fcntl(self.mplayer.stderr, fcntl.F_SETFL, os.O_NONBLOCK)
171)     # set initial information
172)     self.mplayer_name = filename
173)     self.mplayer_pause = False
174)     self.mplayer_speed = 1.0
175)     self.mplayer_last_cmd_timestamp = datetime.datetime.now()
176) 
177)   def mplayerStdouterr(self, err):
178)     """process data from MPlayer stdout (err = False) or stderr (err = True)"""
179)     if self.mplayer is None:
180)       return
181)     # receive data
182)     if err:
183)       txt = self.mplayer.stderr.read()
184)     else:
185)       txt = self.mplayer.stdout.read()
186)     # check if MPlayer exited
187)     if len(txt) == 0:
188)       self.mplayerExit()
189)       return
190)     # MPlayer did not exit
191)     # replace CRs with LFs add data to buffer
192)     txt = txt.replace("\r", "\n")
193)     if err:
194)       buf = self.mplayer_buf_stderr + txt
195)     else:
196)       buf = self.mplayer_buf_stdout + txt
197)     # process complete lines and store remaining data in buffer
198)     lines = buf.split("\n")
199)     for line in lines[:-1]:
200)       self.mplayerLine(line, err)
201)     if err:
202)       self.mplayer_buf_stderr = buf[-1]
203)     else:
204)       self.mplayer_buf_stdout = buf[-1]
205) 
206)   def mplayerStop(self):
207)     """stop MPlayer process in background"""
208)     if self.mplayer is not None:
209)       # send quit command
210)       self.mplayer.stdin.write("quit\n")
211)       # close pipes
212)       self.mplayer.stdin.close()
213)       self.mplayer.stdout.close()
214)       self.mplayer.stderr.close()
215)       # terminate process
216)       self.mplayer.terminate()
217)       self.mplayer.kill()
218)       # close process
219)       self.mplayer.wait()
220)       self.mplayer = None
221)     # clear buffers and information
222)     self.mplayerClear()
223) 
224)   def playlistFind(self, posy_name):
225)     """find file in playlist by PoSy name"""
226)     idx = 0
227)     for file_name in self.playlist:
228)       if self.posyCheckName(posy_name, file_name):
229)         return idx;
230)       idx += 1
231)     return None
232) 
233)   def playlistRead(self, playlist):
234)     """read playlist file"""
235)     # read filenames from playlist
236)     filenames = []
237)     try:
238)       with open(playlist, "rt") as f:
239)         filenames = f.readlines()
240)     except:
241)       return False
242)     filenames = [filename.strip() for filename in filenames]
243)     # convert filenames to absolute paths
244)     playlistdir = os.path.dirname(playlist)
245)     filenames = [os.path.join(playlistdir, filename) for filename in filenames]
246)     # replace playlist
247)     self.playlist = filenames
248)     self.playlist_idx = None
249)     return True
250) 
251)   def playNext(self):
252)     """play next file in playlist"""
253)     # playlist empty -> stop MPlayer
254)     if len(self.playlist) == 0:
255)       self.playlist_idx = None
256)       self.mplayerStop()
257)     # playlist not empty -> play next file
258)     else:
259)       if self.playlist_idx is None:
260)         self.playlist_idx = 0
261)       else:
262)         self.playlist_idx += 1
263)         if self.playlist_idx >= len(self.playlist):
264)           self.playlist_idx = 0
265)       self.mplayerStart(self.playlist[self.playlist_idx])
266) 
267)   def playPosyName(self, posy_name):
268)     """play file by PoSy name (if found)"""
269)     # find file in playlist
270)     idx = self.playlistFind(posy_name)
271)     # file not found -> stop MPlayer
272)     if idx is None:
273)       self.playlist_idx = None
274)       self.mplayerStop()
275)     # file found -> (re-)start MPlayer
276)     else:
277)       self.playlist_idx = idx
278)       self.mplayerStart(self.playlist[idx])
279) 
280)   def posyCheckName(self, posy_name, file_name):
281)     """check if filename matches PoSyName"""
282)     # remove directory part of file name and check
283)     file_name = os.path.basename(file_name)
284)     if file_name == posy_name:
285)       return True
286)     # remove extension and check
287)     file_name = os.path.splitext(file_name)[0]
288)     if file_name == posy_name:
289)       return True
Stefan Schuermans change local file name pref...

Stefan Schuermans authored 10 years ago

290)     # remove ignore prefix and check
291)     m_ignore_prefix = self.re_ignore_prefix.match(file_name)
292)     if m_ignore_prefix and m_ignore_prefix.group(1) == posy_name: