PageRenderTime 49ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/mkvtops3mp4.py

http://mkvtops3mp4.googlecode.com/
Python | 815 lines | 557 code | 146 blank | 112 comment | 84 complexity | 7f9b5712f06311fe214da43f47e15bad MD5 | raw file
  1. #!/usr/bin/python
  2. #
  3. #
  4. # Copyright (c) 2008, Reid Nichol <oddmanout@orthogonalspace.ca>
  5. # All rights reserved.
  6. #
  7. # Redistribution and use in source and binary forms, with or
  8. # without modification, are permitted provided that the following
  9. # conditions are met:
  10. #
  11. # * Redistributions of source code must retain the above
  12. # copyright notice, this list of conditions and the following
  13. # disclaimer.
  14. # * Redistributions in binary form must reproduce the above
  15. # copyright notice, this list of conditions and the following
  16. # disclaimer in the documentation and/or other materials
  17. # provided with the distribution.
  18. # * Neither the name of Reid Nichol nor the names of its
  19. # contributors may be used to endorse or promote products
  20. # derived from this software without specific prior written
  21. # permission.
  22. #
  23. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  24. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  25. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  26. # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  27. # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  28. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  29. # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  30. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  31. # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  32. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  33. # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  34. # POSSIBILITY OF SUCH DAMAGE.
  35. #
  36. # NOTE:
  37. # /usr/bin/python is used as that is the stock binary that comes with
  38. # OSX and it is the binary in which I have tested and the target audience
  39. # will have. It is also used so that if the user has installed Python
  40. # through MacPorts that the "correct" one will be used. If this isn't
  41. # wanted, feel free to change it to the "standard" #!:
  42. #
  43. # /usr/bin/evn python
  44. #
  45. from Tkinter import *
  46. import tkFileDialog, tkMessageBox
  47. import os, sys, math, string
  48. import subprocess, threading
  49. import re
  50. import Queue
  51. status = []
  52. statusLabel = None
  53. statusDisplay = 0
  54. statusDecodeLock = threading.Lock()
  55. statusDecode = 0
  56. guiUpdateTimer = None
  57. file = None
  58. fileInput = None
  59. fileList = []
  60. fileSize = None
  61. fileSizeLabel = None
  62. piecesMenu = None
  63. numPieces = None
  64. sizePerPieceLabel = None
  65. sizePerPiece = None
  66. fixAudio = None
  67. fixAudioMenu = None
  68. bitrate = None
  69. bitrateMenu = None
  70. channels = None
  71. goButton = None
  72. browseButton = None
  73. videoTrack = None
  74. workerThread = None
  75. # DEV NOTE: Put buttons into one variable
  76. # buttons = {}
  77. # buttons['goButton'] = {'button': None, 'activate': 1}
  78. #
  79. # button being the actual button
  80. # activate being whether it should be activated or not
  81. # for selection (peiceds is only this way if
  82. # the file is above a certain limit).
  83. # DEV NOTE: Add debug mode so that when issues come in
  84. # we can get the output easily.
  85. # DEV NOTE: Get rid of unused global vars
  86. def mp4AddAudioOptimise():
  87. global file
  88. try:
  89. p = subprocess.Popen('mp4creator -c ' + os.path.dirname(file) + os.sep + 'audio.aac -interleave -optimize ' + os.path.dirname(file) + os.sep + 'file.mp4', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
  90. for line in p.stdout.readlines():
  91. if re.compile("command\ not\ found").search(line):
  92. changeDecodeStatus(-9, "Couldn't find executable: mp4creator")
  93. raise
  94. p.wait()
  95. return 1
  96. except:
  97. pass
  98. return -1
  99. def mp4AddHint():
  100. global file
  101. try:
  102. p = subprocess.Popen('mp4creator -hint=1 ' + os.path.dirname(file) + os.sep + 'file.mp4', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
  103. for line in p.stdout.readlines():
  104. if re.compile("command\ not\ found").search(line):
  105. changeDecodeStatus(-8, "Couldn't find executable: mp4creator")
  106. raise
  107. p.wait()
  108. return 1
  109. except:
  110. pass
  111. return -1
  112. def mp4AddVideo():
  113. global file, videoTrack
  114. try:
  115. p = subprocess.Popen('mp4creator -create=' + os.path.dirname(file) + os.sep + 'video.h264 -rate=' + str(videoTrack['fps']) + ' ' + os.path.dirname(file) + os.sep + 'file.mp4', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
  116. for line in p.stdout.readlines():
  117. if re.compile("command\ not\ found").search(line):
  118. changeDecodeStatus(-7, "Couldn't find executable: mp4creator")
  119. raise
  120. if re.compile("failed\ assertion.+m_size").search(line):
  121. changeDecodeStatus(-7, 'Video track too large for mp4. Split output into more pieces. ')
  122. raise# Exception('Video track too large for mp4. Split output into more pieces. ')
  123. p.wait()
  124. # video is no longer needed
  125. removeVideo()
  126. return 1
  127. except:
  128. pass
  129. return -1
  130. def getAudio(recurs=0):
  131. global file, channels, bitrate, fixAudio
  132. changeCodec = 0
  133. acodec = ['libfaac', 'faac', 'aac']
  134. try:
  135. chnls = channels.get()
  136. if chnls == '5.1':
  137. chnls = '6'
  138. fa = fixAudio.get()
  139. if fa:
  140. cmds = ['ffmpeg -i ' + file + ' -vn -ac ' + chnls + ' -acodec ' + acodec[recurs] + ' -ab ' + bitrate.get() + 'k ' + os.path.dirname(file) + os.sep + 'audio.wav',
  141. 'ffmpeg -i ' + os.path.dirname(file) + os.sep + 'audio.wav' + ' -vn -ac ' + chnls + ' -acodec ' + acodec[recurs] + ' -ab ' + bitrate.get() + 'k ' + os.path.dirname(file) + os.sep + 'audio.aac'
  142. ]
  143. else:
  144. cmds = ['ffmpeg -i ' + file + ' -vn -ac ' + chnls + ' -acodec ' + acodec[recurs] + ' -ab ' + bitrate.get() + 'k ' + os.path.dirname(file) + os.sep + 'audio.aac']
  145. for cmd in cmds:
  146. p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
  147. for line in p.stdout.readlines():
  148. if re.compile("command\ not\ found").search(line):
  149. changeDecodeStatus(-6, "Couldn't find executable: ffmpeg")
  150. raise
  151. if re.compile("^Unknown\ codec\ \'" + acodec[recurs] + "\'").match(line):
  152. # run through all lines and wait() before lauching
  153. # the other process to prevent zombies
  154. changeCodec = 1
  155. p.wait()
  156. # if we are fixing the audio we need to remove a massive
  157. # file before continuing
  158. removeAudioWav()
  159. # If not the default codec name, give the other one
  160. # as first reported by 'Raoul' on (change to libfaac):
  161. # http://oddmanout.wordpress.com/2008/01/26/converting-an-mkv-h264-file-to-ps3-mp4-without-re-encoding-on-mac-os-x/
  162. #
  163. # And from nabstersblog we get another codec name (change to faac)
  164. # http://nabstersblog.blogspot.com/2008/09/notes-on-converting-h264-to-ps3.html
  165. #
  166. # One has to wonder what the ffmpeg people are thinking not having
  167. # a consistent name for the same output across versions/platforms.
  168. # we got an codec error
  169. if changeCodec:
  170. recurs += 1
  171. if recurs < len(acodec):
  172. # if 1, the try the other one
  173. return getAudio(recurs)
  174. else:
  175. # otherwise both failed and we quit
  176. changeDecodeStatus(-6, "Coudn't find appropriate audio codec.")
  177. raise# Exception("Coudn't find appropriate audio codec.")
  178. return 1
  179. except:
  180. pass
  181. return -1
  182. def correctProfile():
  183. global file
  184. import struct
  185. levelString = struct.pack('b', int('29', 16))
  186. fp = open(os.path.dirname(file) + os.sep + 'video.h264', 'r+b')
  187. if not fp:
  188. changeDecodeStatus(-5, "Couldn't open extracted video to correct profile.")
  189. return -1
  190. fp.seek(7)
  191. fp.write(levelString)
  192. fp.close()
  193. return 1
  194. def extractVideo():
  195. global videoTrack, file
  196. try:
  197. p = subprocess.Popen('mkvextract tracks ' + file + ' ' + str(videoTrack['number']) + ':' + os.path.dirname(file) + os.sep + 'video.h264 > /dev/null', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
  198. for line in p.stdout.readlines():
  199. if re.compile("command\ not\ found").search(line):
  200. changeDecodeStatus(-4, "Couldn't find executable: mkvextract")
  201. raise
  202. p.wait()
  203. return 1
  204. except:
  205. pass
  206. return -1
  207. def getMKVInfo():
  208. global videoTrack, file
  209. track = {'video': 0, 'audio': 0}
  210. # to make sure that two or more runs in
  211. # row run as expected i.e. we don't want
  212. # the previous runs success to impact
  213. # this runs possible failure
  214. videoTrack = None
  215. try:
  216. p = subprocess.Popen('mkvinfo ' + file, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
  217. for line in p.stdout.readlines():
  218. if re.compile("command\ not\ found").search(line):
  219. changeDecodeStatus(-3, "Couldn't find executable: mkvinfo")
  220. raise
  221. if re.compile("^\|\ \+\ A\ track").match(line):
  222. if track['video'] == 1:
  223. videoTrack = track
  224. track = {'video': 0, 'audio': 0}
  225. continue
  226. m = re.compile("^\|\ \ \+\ Track\ number\:\ (\d+)").match(line)
  227. if m:
  228. track['number'] = m.group(1)
  229. continue
  230. m = re.compile("^\|\ \ \+\ Track\ type\:\ (\S+)").match(line)
  231. if m:
  232. if m.group(1) == 'video':
  233. track['video'] = 1
  234. track['audio'] = 0
  235. continue
  236. if m.group(1) == 'audio':
  237. track['video'] = 0
  238. track['audio'] = 1
  239. m = re.compile("^\|\ \ \+\ Default\ duration\:\ \d+\.\d+\S+\ \((\d+)\.(\d+)\ fps").match(line)
  240. if m and m.group(1) and m.group(2):
  241. track['fps'] = float(str(m.group(1)) + '.' + str(m.group(2)))
  242. continue
  243. m = re.compile("^\|\ \ \+\ Codec\ ID\:\ (\S+\/\S+\/\S+)").match(line)
  244. if m and m.group(1):
  245. if track['video'] == 1 and m.group(1) != 'V_MPEG4/ISO/AVC':
  246. changeDecodeStatus(-2, 'Bad video format: ' + m.group(1))
  247. raise# Exception('Bad video format: ' + m.group(1))
  248. track['codecID'] = m.group(1)
  249. p.wait()
  250. # if the video track is the last track, we
  251. # need to catch that
  252. if videoTrack == None and track['video'] == 1:
  253. videoTrack = track
  254. if videoTrack == None:
  255. changeDecodeStatus(-2, 'No video track found. It might be missing or of an inappropriate type.')
  256. raise# Exception('No video track found')
  257. return 1
  258. except:
  259. pass
  260. return -1
  261. def splitFile():
  262. global fileInput, fileList, sizePerPiece, numPieces
  263. # don't split if only doing one piece
  264. if int(numPieces.get()) == 1:
  265. fileList.append(fileInput.get())
  266. return 1
  267. try:
  268. splitMKV = os.path.splitext(fileInput.get())[0] + '-split.mkv'
  269. p = subprocess.Popen('mkvmerge -o ' + splitMKV + ' --split ' + str(sizePerPiece) + 'M ' + fileInput.get(), shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
  270. for line in p.stdout.readlines():
  271. if re.compile("command\ not\ found").search(line):
  272. changeDecodeStatus(-3, "Couldn't find executable: mkvinfo")
  273. raise
  274. m = re.compile('^The\ file\ \'(.*)\'\ has\ been\ opened\ for\ writing\.$').match(line)
  275. if m and m.group(1):
  276. fileList.append(m.group(1))
  277. return 1
  278. except:
  279. pass
  280. return -1
  281. def changeDecodeStatus(old, new):
  282. global statusQueue
  283. statusQueue.put((old, new))
  284. def checkDecodeStatus():
  285. global rootWin, statusQueue, status, goButton, browseButton, bitrateMenu, fixAudioMenu
  286. try:
  287. stat = statusQueue.get(0)
  288. old = stat[0]
  289. new = stat[1]
  290. # error code
  291. if (old < 0):
  292. errorDecoding(new)
  293. # order is, 1) report error, 2) update status
  294. # so one more is coming
  295. rootWin.after(1000, checkDecodeStatus)
  296. return
  297. # This is for changing where we are in
  298. # the conversion process.
  299. if old < 100:
  300. tmpText = status[old]['text']
  301. tmpText = string.replace(tmpText, '*', ' ', 1)
  302. status[old]['text'] = tmpText
  303. tmpText = status[new]['text']
  304. tmpText = string.replace(tmpText, ' ', '*', 1)
  305. status[new]['text'] = tmpText
  306. # For updating something else
  307. else:
  308. if old == 100:
  309. # Set current piece to...
  310. statusLabel['text'] = 'Status (%s/%s):'%(new[0], new[1])
  311. if not (new == 9 or new == 0):
  312. # Once per second should be enough and not
  313. # waste resources.
  314. rootWin.after(1000, checkDecodeStatus)
  315. else:
  316. # re-enable the buttons after the run has
  317. # completed
  318. goButton['state'] = NORMAL
  319. browseButton['state'] = NORMAL
  320. bitrateMenu['state'] = NORMAL
  321. piecesMenu['state'] = NORMAL
  322. fixAudioMenu['state'] = NORMAL
  323. except:
  324. rootWin.after(1000, checkDecodeStatus)
  325. def errorDecoding(msg):
  326. tkMessageBox.showerror(title='Premature End Of Run', message=msg)
  327. def removeAudio():
  328. global file
  329. if os.path.exists(os.path.dirname(file) + os.sep + 'audio.aac'):
  330. os.remove(os.path.dirname(file) + os.sep + 'audio.aac')
  331. def removeAudioWav():
  332. global file
  333. if os.path.exists(os.path.dirname(file) + os.sep + 'audio.wav'):
  334. os.remove(os.path.dirname(file) + os.sep + 'audio.wav')
  335. def removeVideo():
  336. global file
  337. if os.path.exists(os.path.dirname(file) + os.sep + 'video.h264'):
  338. os.remove(os.path.dirname(file) + os.sep + 'video.h264')
  339. def renameMP4():
  340. global file
  341. if os.path.exists(os.path.dirname(file) + os.sep + 'file.mp4'):
  342. old = os.path.dirname(file) + os.sep + 'file.mp4'
  343. new = os.path.splitext(file)[0] + '.mp4'
  344. os.rename(old, new)
  345. # This function is ONLY for cleaning up
  346. # after converting a video that was SPLIT.
  347. # NEVER use otherwise!
  348. def removeMKV():
  349. global file
  350. if os.path.exists(file):
  351. os.remove(file)
  352. def cleanUp():
  353. global fileList
  354. removeAudio()
  355. removeVideo()
  356. renameMP4()
  357. if len(fileList) > 1:
  358. removeMKV()
  359. def startDecoding():
  360. global statusQueue, fileInput, fileList, file
  361. i = 0
  362. # Make the working directory the directory where
  363. # file is and save the one that we started with.
  364. # This will be reset in cleanUp() upon end of
  365. # run for whatever reason.
  366. cwd = os.getcwd()
  367. os.chdir(os.path.dirname(fileInput.get()))
  368. # reset our list in case the user is going
  369. # for a second (or more) time(s)
  370. fileList = []
  371. changeDecodeStatus(0, 1)
  372. if splitFile() < 0:
  373. changeDecodeStatus(1, 0)
  374. cleanUp()
  375. return
  376. i = 1
  377. changeDecodeStatus(100, (str(i), str(len(fileList))))
  378. changeDecodeStatus(1, 2)
  379. while i <= len(fileList):
  380. file = fileList[i - 1]
  381. if getMKVInfo() < 0:
  382. changeDecodeStatus(2, 0)
  383. cleanUp()
  384. return
  385. changeDecodeStatus(2, 3)
  386. if extractVideo() < 0:
  387. changeDecodeStatus(3, 0)
  388. cleanUp()
  389. return
  390. changeDecodeStatus(3, 4)
  391. if correctProfile() < 0:
  392. changeDecodeStatus(4, 0)
  393. cleanUp()
  394. return
  395. changeDecodeStatus(4, 5)
  396. if getAudio() < 0:
  397. changeDecodeStatus(5, 0)
  398. cleanUp()
  399. return
  400. changeDecodeStatus(5, 6)
  401. if mp4AddVideo() < 0:
  402. changeDecodeStatus(6, 0)
  403. cleanUp()
  404. return
  405. changeDecodeStatus(6, 7)
  406. if mp4AddHint() < 0:
  407. changeDecodeStatus(7, 0)
  408. cleanUp()
  409. return
  410. changeDecodeStatus(7, 8)
  411. if mp4AddAudioOptimise() < 0:
  412. changeDecodeStatus(8, 0)
  413. cleanUp()
  414. return
  415. cleanUp()
  416. # if we aren't on the last piece,
  417. # we go back and do the other
  418. # peice(s)
  419. if len(fileList) != i:
  420. changeDecodeStatus(100, (str(i + 1), str(len(fileList))))
  421. changeDecodeStatus(8, 2)
  422. i += 1
  423. changeDecodeStatus(100, ('-', '-'))
  424. changeDecodeStatus(8, 9)
  425. os.chdir(cwd)
  426. def decode():
  427. global workerThread, goButton, browseButton, fixAudioMenu
  428. if not workerThread or not workerThread.isAlive():
  429. goButton['state'] = DISABLED
  430. browseButton['state'] = DISABLED
  431. bitrateMenu['state'] = DISABLED
  432. piecesMenu['state'] = DISABLED
  433. fixAudioMenu['state'] = DISABLED
  434. workerThread = threading.Thread(target=startDecoding)
  435. workerThread.start()
  436. checkDecodeStatus()
  437. # arg is required but not necessary to use
  438. def calcSizePerPiece(arg):
  439. global numPieces, fileSize, sizePerPieceLabel, statusLabel, sizePerPiece
  440. numP = numPieces.get()
  441. sizeP = float(fileSize)/float(numP)
  442. post = ['KB', 'MB', 'GB', 'TB']
  443. # so we tweak that to human readable
  444. tmpSize = float(math.ceil(sizeP))
  445. # so we have the size per peice in MB
  446. sizePerPiece = int(float(math.ceil( ( tmpSize / 1024.0 ) / 1024.0 )))
  447. for p in post:
  448. tmpSize = tmpSize/1024.0
  449. if tmpSize < 1024.0:
  450. sizePerPieceLabel['text'] = 'Size Per Piece: %0.2f %s'%(tmpSize, p)
  451. break
  452. # Can do it directly as this is in the GUI thread.
  453. statusLabel['text'] = 'Status (0/%d):'%int(numP)
  454. def checkForLargeFile():
  455. global fileSize, piecesMenu, numPieces
  456. fourGB = 1024*1024*1024*4
  457. if fileSize > fourGB:
  458. # we must set the number of pieces
  459. # to output. start with 2 pieces
  460. i = 2
  461. while i*fourGB < fileSize:
  462. i += 1
  463. numPieces.set(str(i))
  464. piecesMenu['state'] = NORMAL
  465. tkMessageBox.showinfo(title='Number of Pieces', message='We have set the number of pieces to output to '+str(i)+' to accomidate the 4GB limit of the PS3. You may change it to your liking if you wish. But, doing so may create problems.')
  466. else:
  467. # make sure we set things ok
  468. numPieces.set('1')
  469. #piecesMenu['stat'] = DISABLED
  470. piecesMenu['stat'] = NORMAL
  471. def setFileSize():
  472. global fileInput, fileSize, fileSizeLabel
  473. post = ['KB', 'MB', 'GB', 'TB']
  474. # statinfo.st_size in bytes
  475. statinfo = os.stat(fileInput.get())
  476. fileSize = statinfo.st_size
  477. # so we tweak that to human readable
  478. tmpSize = float(fileSize)
  479. for p in post:
  480. tmpSize = tmpSize/1024.0
  481. if tmpSize < 1024:
  482. fileSizeLabel['text'] = 'Size: %0.2f %s'%(tmpSize, p)
  483. break
  484. def setFile():
  485. global fileInput
  486. tmp = tkFileDialog.askopenfilename(filetypes=[('Matroska Video Files', '*.mkv')])
  487. if tmp != "":
  488. # it won't allow writing otherwise
  489. fileInput['stat'] = NORMAL
  490. fileInput.delete(0, END)
  491. fileInput.insert(INSERT, tmp)
  492. fileInput['stat'] = DISABLED
  493. setFileSize()
  494. checkForLargeFile()
  495. calcSizePerPiece(-1)
  496. def makeGUI():
  497. global rootWin, status, statusLabel, file, fileInput, fileSizeLabel, piecesMenu, numPieces, bitrate, bitrateMenu, goButton, browseButton, sizePerPieceLabel, channels, fixAudio, fixAudioMenu
  498. # input file portion
  499. fileEntryFrame = Frame(rootWin)
  500. Label(fileEntryFrame, text='File: ').pack(side=LEFT)
  501. fileInput = Entry(fileEntryFrame, width=72, state=DISABLED)
  502. fileInput.pack(side=LEFT)
  503. browseButton = Button(fileEntryFrame, text='browse', command=setFile)
  504. browseButton.pack(side=LEFT)
  505. fileEntryFrame.pack(side=TOP, fill=X)
  506. # size/num pieces portion
  507. # needs to be split into two parts to get
  508. # the desired layout
  509. sizePiecesFrame = Frame(rootWin)
  510. sizePiecesFrameA = Frame(sizePiecesFrame)
  511. fileSizeLabel = Label(sizePiecesFrameA, text="Size: ")
  512. fileSizeLabel.pack(side=LEFT)
  513. Label(sizePiecesFrameA, text="Pieces").pack(side=RIGHT)
  514. numPieces = StringVar()
  515. piecesMenu = OptionMenu(sizePiecesFrameA, numPieces, '1', '2', '3', '4', '5', command=calcSizePerPiece)
  516. #piecesMenu['state'] = DISABLED
  517. piecesMenu['state'] = NORMAL
  518. numPieces.set('1')
  519. piecesMenu.pack(side=RIGHT)
  520. Label(sizePiecesFrameA, text="Split Into ").pack(side=RIGHT)
  521. sizePiecesFrameA.pack(side=TOP, fill=X)
  522. sizePiecesFrameB = Frame(sizePiecesFrame)
  523. sizePerPieceLabel = Label(sizePiecesFrameB, text="Size Per Piece: ")
  524. sizePerPieceLabel.pack(side=LEFT)
  525. sizePiecesFrameB.pack(side=BOTTOM, fill=X)
  526. sizePiecesFrame.pack(side=TOP, fill=X)
  527. # audio portion
  528. audioFrame = Frame(rootWin)
  529. audioFrameA = Frame(audioFrame)
  530. Label(audioFrameA, text='Audio:').pack(side=LEFT, fill=X)
  531. audioFrameA.pack(side=TOP, fill=X)
  532. audioFrameB = Frame(audioFrame)
  533. audioFrameC = Frame(audioFrameB)
  534. audioFrame1 = Frame(audioFrameC)
  535. Label(audioFrame1, text='Bit Rate: ').pack(side=LEFT, fill=X)
  536. bitrate = StringVar()
  537. bitrateMenu = OptionMenu(audioFrame1, bitrate, '64', '128', '256', '320')
  538. bitrate.set('64')
  539. bitrateMenu.pack(side=LEFT, fill=X)
  540. Label(audioFrame1, text='kbps').pack(side=LEFT, fill=X)
  541. audioFrame1.pack(side=TOP, fill=X)
  542. audioFrame0 = Frame(audioFrameC)
  543. Label(audioFrame0, text='Fix Audio: ').pack(side=LEFT, fill=X)
  544. fixAudio = IntVar()
  545. fixAudioMenu = Checkbutton(audioFrame0, variable=fixAudio)
  546. fixAudioMenu.pack(side=LEFT, fill=X)
  547. audioFrame0.pack(side=TOP, fill=X)
  548. audioFrameC.pack(side=LEFT, fill=X)
  549. audioFrameD = Frame(audioFrameB)
  550. audioFrame2 = Frame(audioFrameD)
  551. channels = StringVar()
  552. channelsMenu = OptionMenu(audioFrame2, channels, '1', '2', '5.1')
  553. channelsMenu['state'] = DISABLED
  554. channels.set('2')
  555. channelsMenu.pack(side=RIGHT, fill=X)
  556. Label(audioFrame2, text='Channels: ').pack(side=RIGHT, fill=X)
  557. audioFrame2.pack(side=RIGHT, fill=X)
  558. audioFrameD.pack(side=RIGHT, fill=X)
  559. audioFrameB.pack(side=TOP, fill=X)
  560. audioFrame.pack(side=TOP, fill=X)
  561. # status portion
  562. statusFrame = Frame(rootWin)
  563. statusFrame0 = Frame(statusFrame)
  564. statusLabel = Label(statusFrame0, text='Status (-/-):')
  565. statusLabel.pack(side=LEFT, fill=X)
  566. statusFrame0.pack(side=TOP, fill=X)
  567. statusFrameA = Frame(statusFrame)
  568. status.append(Label(statusFrameA, text='* 0: Stopped'))
  569. statusFrameA.pack(side=TOP, fill=X)
  570. statusFrameB = Frame(statusFrame)
  571. status.append(Label(statusFrameB, text=' 1: Splitting'))
  572. statusFrameB.pack(side=TOP, fill=X)
  573. statusFrameC = Frame(statusFrame)
  574. status.append(Label(statusFrameC, text=' 2: Getting MKV Info'))
  575. statusFrameC.pack(side=TOP, fill=X)
  576. statusFrameD = Frame(statusFrame)
  577. status.append(Label(statusFrameD, text=' 3: Extracting Video'))
  578. statusFrameD.pack(side=TOP, fill=X)
  579. statusFrameE = Frame(statusFrame)
  580. status.append(Label(statusFrameE, text=' 4: Correcting Profile'))
  581. statusFrameE.pack(side=TOP, fill=X)
  582. statusFrameF = Frame(statusFrame)
  583. status.append(Label(statusFrameF, text=' 5: Extracting/Converting Audio'))
  584. statusFrameF.pack(side=TOP, fill=X)
  585. statusFrameG = Frame(statusFrame)
  586. status.append(Label(statusFrameG, text=' 6: Adding Video To MP4'))
  587. statusFrameG.pack(side=TOP, fill=X)
  588. statusFrameH = Frame(statusFrame)
  589. status.append(Label(statusFrameH, text=' 7: Hinting Video'))
  590. statusFrameH.pack(side=TOP, fill=X)
  591. statusFrameI = Frame(statusFrame)
  592. status.append(Label(statusFrameI, text=' 8: Adding Audio And Optimising MP4'))
  593. statusFrameI.pack(side=TOP, fill=X)
  594. statusFrameJ = Frame(statusFrame)
  595. status.append(Label(statusFrameJ, text=' 9: Done'))
  596. statusFrameJ.pack(side=TOP, fill=X)
  597. for i in status:
  598. i.pack(side=LEFT, fill=X)
  599. statusFrame.pack(side=TOP, fill=X)
  600. # go button
  601. goFrame = Frame(rootWin)
  602. goButton = Button(goFrame, text='Start', command=decode)
  603. goButton.pack(side=BOTTOM)
  604. goFrame.pack(side=BOTTOM, fill=X)
  605. # Code from Dave Opstad to hide the Console window that
  606. # py2app annoyingly thinks is so necessary.
  607. #
  608. # http://coding.derkeiler.com/Archive/Python/comp.lang.python/2006-10/msg00414.html
  609. def hideConsole():
  610. global rootWin
  611. if (sys.platform != "win32") and hasattr(sys, 'frozen'):
  612. rootWin.tk.call('console', 'hide')
  613. if __name__ == '__main__':
  614. global rootWin, statusQueue
  615. statusQueue = Queue.Queue()
  616. rootWin = Tk()
  617. rootWin.title("MKV to PS3 MP4")
  618. makeGUI()
  619. # debuging needs the console
  620. hideConsole()
  621. rootWin.mainloop()