pyTivo
[pyTivo/krkeegan.git] / plugins / video / transcode.py
blob6e48fc719f719be0c812ec9baea3bedc1cf26d9f
1 import subprocess, shutil, os, re, sys, ConfigParser, time, lrucache
3 from Config import config
5 info_cache = lrucache.LRUCache(1000)
7 FFMPEG = config.get('Server', 'ffmpeg')
8 #SCRIPTDIR = os.path.dirname(__file__)
9 #FFMPEG = os.path.join(SCRIPTDIR, 'ffmpeg_mp2.exe')
10 #FFMPEG = '/usr/bin/ffmpeg'
12 # XXX BIG HACK
13 # subprocess is broken for me on windows so super hack
14 def patchSubprocess():
15 o = subprocess.Popen._make_inheritable
17 def _make_inheritable(self, handle):
18 if not handle: return subprocess.GetCurrentProcess()
19 return o(self, handle)
21 subprocess.Popen._make_inheritable = _make_inheritable
22 mswindows = (sys.platform == "win32")
23 if mswindows:
24 patchSubprocess()
26 def output_video(inFile, outFile):
27 if tivo_compatable(inFile):
28 f = file(inFile, 'rb')
29 shutil.copyfileobj(f, outFile)
30 f.close()
31 else:
32 transcode(inFile, outFile)
34 def transcode(inFile, outFile):
35 cmd = [FFMPEG, '-i', inFile, '-vcodec', 'mpeg2video', '-r', '29.97', '-b', '4096K'] + select_aspect(inFile) + ['-comment', 'pyTivo.py', '-ac', '2', '-ab', '192','-ar', '44100', '-f', 'vob', '-' ]
36 ffmpeg = subprocess.Popen(cmd, stdout=subprocess.PIPE)
37 try:
38 shutil.copyfileobj(ffmpeg.stdout, outFile)
39 except:
40 kill(ffmpeg.pid)
42 def select_aspect(inFile):
43 type, width, height, fps, millisecs = video_info(inFile)
45 d = gcd(height,width)
46 ratio = (width*100)/height
47 rheight, rwidth = height/d, width/d
49 if (rheight, rwidth) in [(4, 3), (10, 11), (15, 11), (59, 54), (59, 72), (59, 36), (59, 54)]:
50 return ['-aspect', '4:3', '-s', '720x480']
51 elif (rheight, rwidth) in [(16, 9), (20, 11), (40, 33), (118, 81), (59, 27)]:
52 return ['-aspect', '16:9', '-s', '720x480']
53 #If video is nearly 4:3 or 16:9 go ahead and strech it
54 elif ((ratio <= 141) and (ratio >= 125)):
55 return ['-aspect', '4:3', '-s', '720x480']
56 elif ((ratio <= 185) and (ratio >= 169)):
57 return ['-aspect', '16:9', '-s', '720x480']
58 else:
59 settings = []
60 settings.append('-aspect')
61 settings.append('4:3')
62 #If video is wider than 4:3 add top and bottom padding
63 if (ratio > 133):
65 endHeight = (720*height)/width
66 if endHeight % 2:
67 endHeight -= 1
69 settings.append('-s')
70 settings.append('720x' + str(endHeight))
72 topPadding = ((480 - endHeight)/2)
73 if topPadding % 2:
74 topPadding -= 1
76 settings.append('-padtop')
77 settings.append(str(topPadding))
78 bottomPadding = (480 - endHeight) - topPadding
79 settings.append('-padbottom')
80 settings.append(str(bottomPadding))
82 return settings
83 #If video is taller than 4:3 add left and right padding, this is rare
84 else:
85 endWidth = (480*width)/height
86 if endWidth % 2:
87 endWidth -= 1
89 settings.append('-s')
90 settings.append(str(endWidth) + 'x480')
92 leftPadding = ((720 - endWidth)/2)
93 if leftPadding % 2:
94 leftPadding -= 1
96 settings.append('-padleft')
97 settings.append(str(leftPadding))
98 rightPadding = (720 - endWidth) - leftPadding
99 settings.append('-padright')
100 settings.append(str(rightPadding))
101 return settings
103 def tivo_compatable(inFile):
104 suportedModes = [[720, 480], [704, 480], [544, 480], [480, 480], [352, 480]]
105 type, width, height, fps, millisecs = video_info(inFile)
106 #print type, width, height, fps, millisecs
108 if (inFile[-5:]).lower() == '.tivo':
109 return True
111 if not type == 'mpeg2video':
112 #print 'Not Tivo Codec'
113 return False
115 if not fps == '29.97':
116 #print 'Not Tivo fps'
117 return False
119 for mode in suportedModes:
120 if (mode[0], mode[1]) == (width, height):
121 #print 'Is TiVo!'
122 return True
123 #print 'Not Tivo dimensions'
124 return False
126 def video_info(inFile):
127 if inFile in info_cache:
128 return info_cache[inFile]
130 if (inFile[-5:]).lower() == '.tivo':
131 info_cache[inFile] = (True, True, True, True, True)
132 return True, True, True, True, True
134 cmd = [FFMPEG, '-i', inFile ]
135 ffmpeg = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
137 # wait 4 sec if ffmpeg is not back give up
138 for i in range(80):
139 time.sleep(.05)
140 if not ffmpeg.poll() == None:
141 break
143 if ffmpeg.poll() == None:
144 kill(ffmpeg.pid)
145 info_cache[inFile] = (None, None, None, None, None)
146 return None, None, None, None, None
148 output = ffmpeg.stderr.read()
150 durre = re.compile(r'.*Duration: (.{2}):(.{2}):(.{2})\.(.),')
151 d = durre.search(output)
153 rezre = re.compile(r'.*Video: ([^,]+),.*')
154 x = rezre.search(output)
155 if x:
156 codec = x.group(1)
157 else:
158 info_cache[inFile] = (None, None, None, None, None)
159 return None, None, None, None, None
161 rezre = re.compile(r'.*Video: .+, (\d+)x(\d+),.*')
162 x = rezre.search(output)
163 if x:
164 width = int(x.group(1))
165 height = int(x.group(2))
166 else:
167 info_cache[inFile] = (None, None, None, None, None)
168 return None, None, None, None, None
170 rezre = re.compile(r'.*Video: .+, (.+) fps.*')
171 x = rezre.search(output)
172 if x:
173 fps = x.group(1)
174 else:
175 info_cache[inFile] = (None, None, None, None, None)
176 return None, None, None, None, None
178 rezre = re.compile(r'.*film source: (\d+).*')
179 x = rezre.search(output.lower())
180 if x:
181 fps = x.group(1)
183 millisecs = ((int(d.group(1))*3600) + (int(d.group(2))*60) + int(d.group(3)))*1000 + (int(d.group(4))*100)
184 info_cache[inFile] = (codec, width, height, fps, millisecs)
185 return codec, width, height, fps, millisecs
187 def suported_format(inFile):
188 if video_info(inFile)[0]:
189 return video_info(inFile)[4]
190 else:
191 return False
193 def kill(pid):
194 if mswindows:
195 win32kill(pid)
196 else:
197 import os, signal
198 os.kill(pid, signal.SIGKILL)
200 def win32kill(pid):
201 import ctypes
202 handle = ctypes.windll.kernel32.OpenProcess(1, False, pid)
203 ctypes.windll.kernel32.TerminateProcess(handle, -1)
204 ctypes.windll.kernel32.CloseHandle(handle)
206 def gcd(a,b):
207 while b:
208 a, b = b, a % b
209 return a