PageRenderTime 99ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/rip.py

https://bitbucket.org/danielfalk/ripencode
Python | 376 lines | 372 code | 3 blank | 1 comment | 1 complexity | 07013d24dde588b9ead3f9b1cffd784d MD5 | raw file
  1. #!/usr/bin/env python
  2. import sys
  3. import commands
  4. import getopt
  5. import re
  6. import string
  7. import math
  8. import os
  9. import dbus
  10. def main():
  11. os.nice(15)
  12. settings = {}
  13. settings["title"] = "0"
  14. settings["angle"] = 1
  15. settings["path"] = "/dev/dvd"
  16. settings["disc_title"] = ""
  17. settings["chapter"] = 1
  18. settings["output_directory"] = "~/dvd/<disc_title>/<title>"
  19. settings["nosubs"] = False
  20. settings = read_args(settings)
  21. got_title_info = False
  22. if( not os.path.exists(settings["path"]) ) :
  23. usage_and_exit("no dvd found at path: " + settings["path"])
  24. title_info = {}
  25. if settings["disc_title"] != "" :
  26. title_info["disc_title"] = settings["disc_title"]
  27. else :
  28. title_info = get_title_info(settings["path"])
  29. got_title_info = True
  30. print "Disc Title: " + title_info["disc_title"]
  31. output_dir = settings["output_directory"].replace("<disc_title>", title_info["disc_title"])
  32. output_dir = os.path.normpath(os.path.expanduser(output_dir))
  33. suspend = SuspendInhibit("rip", "Ripping a DVD", SuspendInhibit.SUSPENDING)
  34. try :
  35. if(settings["title"] == "0") :
  36. if not got_title_info :
  37. title_info = get_title_info(settings["path"])
  38. got_title_info = True
  39. title = parse_int(title_info["longest_title"])
  40. output_dir = output_dir.replace("<title>", str(title).zfill(2))
  41. grab_title(title, settings, title_info["disc_title"], output_dir)
  42. else :
  43. have_valid_title = False
  44. for title in settings["title"].split(",") :
  45. title = parse_int(title)
  46. output_dir1 = output_dir.replace("<title>", str(title).zfill(2))
  47. if got_title_info :
  48. if( not title in title_info["title_numbers"] ) :
  49. continue
  50. have_valid_title = True
  51. print "Title: " + str(title)
  52. grab_title(title, settings, title_info["disc_title"], output_dir1)
  53. if( not have_valid_title ) :
  54. usage_and_exit("You must specify at least one valid title.")
  55. print "Complete."
  56. os.system("eject " + settings["path"])
  57. finally :
  58. suspend.uninhibit()
  59. def save_chapters(title_num, disc_path, output_directory) :
  60. print "saving chapters to directory: " + output_directory
  61. if not os.path.exists(output_directory) :
  62. os.makedirs(output_directory)
  63. output_file = os.path.join(output_directory, "chapters.txt")
  64. commands.getoutput("dvdxchap -t %d %s > %s" % (title_num, disc_path, output_file))
  65. class SuspendInhibit :
  66. LOGGING_OUT = 1
  67. USER_SWITCHING = 2
  68. SUSPENDING = 4
  69. IDLE_SESSION = 8
  70. def __init__(self, program, activity, flags=SUSPENDING) :
  71. self.dev = None
  72. try:
  73. bus = dbus.Bus(dbus.Bus.TYPE_SESSION)
  74. #last worked on jaunty
  75. #devobj = bus.get_object('org.freedesktop.PowerManagement', '/org/freedesktop/PowerManagement/Inhibit')
  76. #self.dev = dbus.Interface(devobj, "org.freedesktop.PowerManagement.Inhibit")
  77. devobj = bus.get_object('org.gnome.SessionManager', '/org/gnome/SessionManager')
  78. self.dev = dbus.Interface(devobj, 'org.gnome.SessionManager')
  79. self.cookie = self.dev.Inhibit(program, 0, activity, flags)
  80. except:
  81. print "Can't inhibit suspend"
  82. def uninhibit(self) :
  83. if self.dev != None:
  84. self.dev.Uninhibit(self.cookie)
  85. class TitleInfo :
  86. _file = ""
  87. _subtitles = None
  88. _text = None
  89. _audio = None
  90. title = 0
  91. working_directory = ""
  92. disc_path = ""
  93. def __init__(self, working_directory, disc_path = None, title_num = None) :
  94. self.working_directory = working_directory
  95. if not os.path.exists(working_directory) :
  96. os.makedirs(working_directory)
  97. self.title = title_num
  98. self.disc_path = disc_path
  99. self._file = os.path.join(working_directory, "disc_info.txt")
  100. def save_info(self) :
  101. self.get_text()
  102. def get_text(self) :
  103. if self._text is not None :
  104. return self._text
  105. elif os.path.exists(self._file) :
  106. return read_text_file(self._file)
  107. else :
  108. if self.title == None:
  109. print "No disc title and file not found at", self._file
  110. sys.exit(1)
  111. self._text = commands.getoutput(
  112. "mplayer dvd://%d -dvd-device %s -vo dummy -ao dummy -identify 2>/dev/null"
  113. % (self.title, self.disc_path))
  114. write_text_file(self._file, self._text)
  115. return self._text
  116. def get_subtitles(self, lang = None) :
  117. if self._subtitles == None :
  118. matches = re.findall(r"subtitle\b.+(\d+) language: (\w+)", self.get_text())
  119. self._subtitles = [{"id" : int(m[0]), "lang" : m[1].strip().lower()} for m in matches]
  120. if lang is not None :
  121. ret = []
  122. for sub in self._subtitles :
  123. if sub["lang"] in lang :
  124. ret.append(sub)
  125. return ret
  126. return self._subtitles or []
  127. def get_audio_by_id(self, id) :
  128. id = int(id)
  129. for track in self.get_audio() :
  130. if int(track["id"]) == id :
  131. return track
  132. raise Exception, "track %d not found" % id
  133. def get_video_seconds(self) :
  134. m = re.search(r"^ID_LENGTH=(\d+)", self.get_text(), re.M)
  135. if m :
  136. return int(m.group(1))
  137. else :
  138. raise Exception, "Couldn't determine the number of seconds for the video."
  139. def get_default_audio_id(self) :
  140. pattern = r"^ID_AUDIO_ID=(\d+)"
  141. matches = re.findall(pattern, self.get_text(), re.M)
  142. print "default audio: ", matches[-1]
  143. return int(matches[-1])
  144. def get_audio(self, lang = None) :
  145. if self._audio == None :
  146. pattern = r"audio stream: (\d+) format: .+ \((.+)\) language: (\w+) aid: (\d+)."
  147. matches = re.findall(pattern, self.get_text())
  148. self._audio = map(
  149. lambda m :
  150. {"stream" : int(m[0]), "format" : m[1], "lang" : m[2], "id" : m[3]}
  151. , matches)
  152. id = self.get_default_audio_id()
  153. for track in self._audio :
  154. if track["id"] == id :
  155. yield track
  156. break
  157. for track in self._audio :
  158. if (lang is None or track["lang"] in lang) and track["id"] != id :
  159. yield track
  160. retval = self._audio
  161. """
  162. if lang is not None :
  163. retval = filter(
  164. lambda track :
  165. track["lang"] in lang
  166. , retval)
  167. return retval
  168. """
  169. def save_subtitles(title_info, chapter, lang) :
  170. print "saving subtitles to directory: " + title_info.working_directory
  171. subtitles = title_info.get_subtitles(lang)
  172. if len(subtitles) == 0 :
  173. subtitles = title_info.get_subtitles()
  174. for sub in subtitles :
  175. output_file = os.path.join(title_info.working_directory, "subs%s" % sub["id"])
  176. cmd = "mencoder dvd://%d -chapter %s -dvd-device %s -nosound -ovc frameno -o /dev/null -sid %s -vobsubout %s"
  177. cmd = cmd % (title_info.title, chapter, title_info.disc_path, sub["id"], output_file)
  178. commands.getoutput(cmd)
  179. def save_audio(title_info, lang) :
  180. print "saving audio to directory: " + title_info.working_directory
  181. audio_tracks = list(title_info.get_audio(lang))
  182. try:
  183. if not any(track["id"] == 128 for track in audio_tracks):
  184. #When 128 doesn't exist as a track, an error is thrown, so nothing's added.
  185. audio_tracks.insert(0, title_info.get_audio_by_id(128))
  186. except: pass
  187. if len(audio_tracks) == 0 :
  188. audio_tracks = list(title_info.get_audio())
  189. if len(audio_tracks) == 0 :
  190. raise Exception, "still found zero audio tracks!"
  191. output_file = os.path.join(title_info.working_directory, "audiostreamlist")
  192. streamlist = [track["id"] for track in audio_tracks]
  193. write_text_file(output_file, "\n".join(streamlist))
  194. def grab_title(title_num, settings, disc_title, output_directory) :
  195. disc_path = settings["path"]
  196. angle = settings["angle"]
  197. chapter = settings["chapter"]
  198. print "ripping title to directory: " + output_directory
  199. if not os.path.exists(output_directory) :
  200. os.makedirs(output_directory)
  201. output_file = os.path.join(output_directory, "%s-%d-%d.vob" % (disc_title, title_num, angle))
  202. LANG = ["en", "unknown", "eng"]
  203. info = TitleInfo(output_directory, disc_path, title_num)
  204. info.save_info()
  205. save_audio(info, LANG)
  206. save_chapters(title_num, disc_path, output_directory)
  207. cmd = "mplayer dvd://%d -v -chapter %s -dumpstream -dumpfile '%s' -dvd-device %s 2> /dev/null" \
  208. % (title_num, chapter, output_file, disc_path)
  209. print cmd
  210. pipe = os.popen(cmd)
  211. while 1:
  212. line = pipe.readline()
  213. if not line:
  214. break
  215. if line.find("DVD next cell") >= 0 :
  216. print line,
  217. if not settings["nosubs"] :
  218. save_subtitles(info, chapter, LANG)
  219. def write_text_file(path, to_write) :
  220. f = open(path, 'w')
  221. f.write(to_write)
  222. f.close()
  223. def read_text_file(path) :
  224. if os.path.isfile(path) :
  225. f=open(path, 'r')
  226. line = f.read()
  227. f.close()
  228. return line
  229. else:
  230. return ""
  231. def get_title_info(dvd_path) :
  232. output = commands.getoutput("lsdvd " + dvd_path)
  233. disc_title = re.search("Disc Title: (\w+)", output, re.I).group(1).lower()
  234. re_title = re.compile("Title: (\d+),", re.I | re.M)
  235. title_numbers = re_title.findall(output)
  236. title_numbers = [parse_int(title_num) for title_num in title_numbers]
  237. longest_title = re.search(r"Longest track: (\d+)", output, re.I).group(1)
  238. return {"disc_title": disc_title, "longest_title": longest_title, "title_numbers": title_numbers}
  239. def parse_int(s) :
  240. m = re.search(r"-?\d+", s)
  241. if( not m ) :
  242. return 0
  243. return int(m.group())
  244. def read_args(settings) :
  245. try:
  246. opts, args = getopt.gnu_getopt(sys.argv[1:], "?c:t:l:a:d:o",
  247. ["help",
  248. "title=",
  249. "angle=",
  250. "chapter=",
  251. "dvd-path=",
  252. "output-directory=",
  253. "disc-title=",
  254. "nosubs",
  255. ])
  256. except getopt.GetoptError, msg:
  257. usage_and_exit(msg)
  258. for opt, arg in opts:
  259. if opt in ("-?", "--help"):
  260. usage_and_exit()
  261. elif opt in ("-t", "--title"):
  262. settings["title"] = arg
  263. elif opt in ("-a", "--angle"):
  264. settings["angle"] = arg
  265. elif opt in ("-c", "--chapter"):
  266. settings["chapter"] = arg
  267. elif opt in ("-d", "--dvd-path"):
  268. settings["path"] = arg
  269. elif opt in ("-o", "--output-directory"):
  270. settings["output_directory"] = arg
  271. elif opt in ("-l", "--disc-title"):
  272. settings["disc_title"] = arg
  273. elif opt in ("--nosubs") :
  274. settings["nosubs"] = True
  275. else :
  276. print "unknown arg: " + opt
  277. if len(args) > 0 :
  278. usage_and_exit("unknown arguments: " + str(args))
  279. return settings
  280. def usage_and_exit(errmsg):
  281. """Print a usage message, plus an ERRMSG (if provided), then exit.
  282. If ERRMSG is provided, the usage message is printed to stderr and
  283. the script exits with a non-zero error code. Otherwise, the usage
  284. message goes to stdout, and the script exits with a zero
  285. errorcode."""
  286. if errmsg is None:
  287. stream = sys.stdout
  288. else:
  289. stream = sys.stderr
  290. print >> stream, __doc__
  291. if errmsg:
  292. print >> stream, "\nError: %s" % (errmsg)
  293. sys.exit(2)
  294. sys.exit(0)
  295. if __name__ == "__main__":
  296. main()