Advertisement
BSDG33KCLUB

sc.py (screencast python script)

May 29th, 2014
236
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.96 KB | None | 0 0
  1. #!/usr/bin/env python
  2. #usage: python sc.py in console
  3. ## DragonFly BSD Screencasting script for ffmpeg ##
  4.  
  5. # Easy-to-change defaults for users
  6. DEFAULT_FPS = 10
  7. DEFAULT_FILE_EXTENSION = ".avi"
  8. ACCEPTABLE_FILE_EXTENSIONS = [".avi", ".mp4", ".mov", ".mkv", ".ogv"]
  9. DEFAULT_CAPTURE_AUDIO_DEVICE = "/dev/dsp1"
  10. DEFAULT_CAPTURE_DISPLAY_DEVICE = ""
  11. DEFAULT_AUDIO_CODEC = "mp3"
  12. DEFAULT_VIDEO_CODEC = "mpeg4"
  13.  
  14. import os
  15. import sys
  16. import os.path
  17. import glob
  18. import time
  19. import random
  20. import tempfile
  21. import optparse
  22. import subprocess
  23. import re
  24.  
  25.  
  26. PYTHON_3 = (sys.version_info[0] == 3)
  27.  
  28.  
  29. # Optional packages
  30. try:
  31. import Tkinter
  32. have_tk = True
  33. except ImportError:
  34. have_tk = False
  35.  
  36. try:
  37. import multiprocessing
  38. have_multiproc = True
  39. except ImportError:
  40. have_multiproc = False
  41.  
  42.  
  43. # Video codec lines
  44. vcodecs = {}
  45. vcodecs["h264"] = ["-vcodec", "libx264", "-preset", "medium"]
  46. vcodecs["h264_fast"] = ["-vcodec", "libx264", "-preset", "ultrafast", "-g", "15", "-crf", "0", "-pix_fmt", "yuv444p"]
  47. vcodecs["mpeg4"] = ["-vcodec", "mpeg4", "-qmax", "1", "-qmin", "1"]
  48. #vcodecs["xvid"] = ["-vcodec", "libxvid", "-b", "40000kb"]
  49. vcodecs["huffyuv"] = ["-vcodec", "huffyuv"]
  50. vcodecs["vp8"] = ["-vcodec", "libvpx", "-qmax", "2", "-qmin", "1"]
  51. vcodecs["theora"] = ["-vcodec", "libtheora", "-b", "40000kb"]
  52. #vcodecs["dirac"] = ["-vcodec", "libschroedinger", "-b", "40000kb"]
  53.  
  54. # Audio codec lines
  55. acodecs = {}
  56. acodecs["pcm"] = ["-acodec", "pcm_s16le"]
  57. #acodecs["flac"] = ["-acodec", "flac"]
  58. acodecs["vorbis"] = ["-acodec", "libvorbis", "-ab", "320k"]
  59. acodecs["mp3"] = ["-acodec", "libmp3lame", "-ab", "320k"]
  60. acodecs["aac"] = ["-acodec", "libfdk-aac", "-ab", "320k"]
  61.  
  62.  
  63. def capture_line(fps, x, y, height, width, display_device, audio_device, video_codec, audio_codec, output_path):
  64. """ Returns the command line to capture video+audio, in a list form
  65. compatible with Popen.
  66. """
  67. threads = 2
  68. if have_multiproc:
  69. # Detect the number of threads we have available
  70. threads = multiprocessing.cpu_count()
  71. line = ["ffmpeg",
  72. "-f", "oss",
  73. "-ac", "2",
  74. "-i", str(audio_device),
  75. "-f", "x11grab",
  76. "-r", str(fps),
  77. "-s", "%dx%d" % (int(height), int(width)),
  78. "-i", display_device + "+" + str(x) + "," + str(y)]
  79. line += acodecs[audio_codec]
  80. line += vcodecs[video_codec]
  81. line += ["-threads", str(threads), str(output_path)]
  82. return line
  83.  
  84.  
  85. def video_capture_line(fps, x, y, height, width, display_device, video_codec, output_path):
  86. """ Returns the command line to capture video (no audio), in a list form
  87. compatible with Popen.
  88. """
  89. threads = 2
  90. if have_multiproc:
  91. # Detect the number of threads we have available
  92. threads = multiprocessing.cpu_count()
  93.  
  94. line = ["mpeg",
  95. "-f", "x11grab",
  96. "-r", str(fps),
  97. "-s", "%dx%d" % (int(height), int(width)),
  98. "-i", display_device + "+" + str(x) + "," + str(y)]
  99. line += vcodecs[video_codec]
  100. line += ["-threads", str(threads), str(output_path)]
  101. return line
  102.  
  103.  
  104. def audio_capture_line(audio_device, audio_codec, output_path):
  105. """ Returns the command line to capture audio (no video), in a list form
  106. compatible with Popen.
  107. """
  108. line = ["ffmpeg",
  109. "-f", "oss",
  110. "-ac", "2",
  111. "-i", str(audio_device)]
  112. line += acodecs[audio_codec]
  113. line += [str(output_path)]
  114. return line
  115.  
  116.  
  117. def get_desktop_resolution():
  118. """ Returns the resolution of the desktop as a tuple.
  119. """
  120. if have_tk:
  121. # Use tk to get the desktop resolution if we have it
  122. root = Tkinter.Tk()
  123. width = root.winfo_screenwidth()
  124. height = root.winfo_screenheight()
  125. root.destroy()
  126. return (width, height)
  127. else:
  128. # Otherwise call xdpyinfo and parse its output
  129. try:
  130. proc = subprocess.Popen("xdpyinfo", stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  131. except OSError:
  132. return None
  133. out, err = proc.communicate()
  134. if PYTHON_3:
  135. lines = str(out).split("\\n")
  136. else:
  137. lines = out.split("\n")
  138. for line in lines:
  139. if "dimensions" in line:
  140. line = re.sub(".*dimensions:[ ]*", "", line)
  141. line = re.sub("[ ]*pixels.*", "", line)
  142. wh = line.strip().split("x")
  143. return (int(wh[0]), int(wh[1]))
  144.  
  145.  
  146. def get_window_position_and_size():
  147. """ Prompts the user to click on a window, and returns the window's
  148. position and size.
  149. """
  150. try:
  151. proc = subprocess.Popen("xwininfo", stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  152. except OSError:
  153. return None
  154. out, err = proc.communicate()
  155. if PYTHON_3:
  156. lines = str(out).split("\\n")
  157. else:
  158. lines = out.split("\n")
  159. x = 0
  160. y = 0
  161. w = 0
  162. h = 0
  163. xt = False
  164. yt = False
  165. wt = False
  166. ht = False
  167. for line in lines:
  168. if "Absolute upper-left X:" in line:
  169. x = int(re.sub("[^0-9]", "", line))
  170. xt = True
  171. elif "Absolute upper-left Y:" in line:
  172. y = int(re.sub("[^0-9]", "", line))
  173. yt = True
  174. elif "Width:" in line:
  175. w = int(re.sub("[^0-9]", "", line))
  176. wt = True
  177. elif "Height:" in line:
  178. h = int(re.sub("[^0-9]", "", line))
  179. ht = True
  180. if xt and yt and wt and ht:
  181. return (x, y, w, h)
  182. else:
  183. return None
  184.  
  185.  
  186. def get_default_output_path():
  187. """ Creates a default output file path.
  188. Pattern: out_####.ext
  189. """
  190. filenames = glob.glob("out_????" + DEFAULT_FILE_EXTENSION)
  191. for i in range(1, 9999):
  192. name = "out_" + str(i).rjust(4,'0') + DEFAULT_FILE_EXTENSION
  193. tally = 0
  194. for f in filenames:
  195. if f == name:
  196. tally += 1
  197. if tally == 0:
  198. return name
  199. return "out_9999" + DEFAULT_FILE_EXTENSION
  200.  
  201.  
  202. def print_codecs():
  203. """ Prints a list of the available audio/video codecs.
  204. """
  205. a = []
  206. v = []
  207. for i in acodecs:
  208. a += [i]
  209. for i in vcodecs:
  210. v += [i]
  211. a.sort()
  212. v.sort()
  213.  
  214. print("Audio codecs:")
  215. for i in a:
  216. print(" " + str(i))
  217.  
  218. print("Video codecs:")
  219. for i in vcodecs:
  220. print(" " + str(i))
  221.  
  222. if __name__ == "__main__":
  223. # Set up default file path
  224. out_path = get_default_output_path()
  225.  
  226. # Parse command line arguments
  227. parser = optparse.OptionParser(usage="%prog [options] [output_file" + DEFAULT_FILE_EXTENSION + "]")
  228. parser.add_option("-w", "--capture-window", action="store_true", dest="capture_window",
  229. default=False,
  230. help="prompt user to click on a window to capture")
  231. parser.add_option("-n", "--no-audio", action="store_true", dest="no_audio",
  232. default=False,
  233. help="don't capture audio")
  234. parser.add_option("-r", "--fps", dest="fps",
  235. type="int", default=DEFAULT_FPS,
  236. help="frame rate to capture video at. Default: " + str(DEFAULT_FPS))
  237. parser.add_option("-p", "--position", dest="xy", metavar="XxY",
  238. type="string", default=None,
  239. help="upper left corner of the capture area (in pixels from the upper left of the screen). Default: 0x0")
  240. parser.add_option("-s", "--size", dest="size",
  241. type="string", default=None, metavar="WIDTHxHEIGHT",
  242. help="resolution of the capture area (in pixels). Default: entire desktop")
  243. parser.add_option("--crop-top", dest="crop_top",
  244. type="int", default=0,
  245. help="number of pixels to crop off the top of the capture area")
  246. parser.add_option("--crop-bottom", dest="crop_bottom",
  247. type="int", default=0,
  248. help="number of pixels to crop off the bottom of the capture area")
  249. parser.add_option("--crop-left", dest="crop_left",
  250. type="int", default=0,
  251. help="number of pixels to crop off the left of the capture area")
  252. parser.add_option("--crop-right", dest="crop_right",
  253. type="int", default=0,
  254. help="number of pixels to crop off the right of the capture area")
  255. parser.add_option("-a", "--audio-device", dest="audio_device",
  256. default=DEFAULT_CAPTURE_AUDIO_DEVICE,
  257. help="the audio device to capture from (eg. hw:0). Default: " + DEFAULT_CAPTURE_AUDIO_DEVICE)
  258. parser.add_option("-d", "--display-device", dest="display_device",
  259. default=DEFAULT_CAPTURE_DISPLAY_DEVICE,
  260. help="the display device to capture from (eg. :0.0). Default: " + DEFAULT_CAPTURE_DISPLAY_DEVICE)
  261. parser.add_option("--acodec", dest="acodec",
  262. default=DEFAULT_AUDIO_CODEC,
  263. help="the audio codec to encode with. Default: " + DEFAULT_AUDIO_CODEC)
  264. parser.add_option("--vcodec", dest="vcodec",
  265. default=DEFAULT_VIDEO_CODEC,
  266. help="the video codec to encode with. Default: " + DEFAULT_VIDEO_CODEC)
  267. parser.add_option("--codecs", action="store_true", dest="list_codecs",
  268. default=False,
  269. help="display the available audio and video codecs")
  270.  
  271. opts, args = parser.parse_args()
  272.  
  273. # Print list of codecs, if requested
  274. if opts.list_codecs:
  275. print_codecs()
  276. exit(0)
  277.  
  278. # Output file path
  279. if len(args) >= 1:
  280. out_path = args[0]
  281. if out_path[-4:] not in ACCEPTABLE_FILE_EXTENSIONS:
  282. out_path += DEFAULT_FILE_EXTENSION
  283.  
  284. # Get desktop resolution
  285. try:
  286. dres = get_desktop_resolution()
  287. except:
  288. print("Error: unable to determine desktop resolution.")
  289. raise
  290.  
  291. # Capture values
  292. fps = opts.fps
  293. if opts.capture_window:
  294. print("Please click on a window to capture.")
  295. x, y, width, height = get_window_position_and_size()
  296. else:
  297. if opts.xy:
  298. if re.match("^[0-9]*x[0-9]*$", opts.xy.strip()):
  299. xy = opts.xy.strip().split("x")
  300. x = int(xy[0])
  301. y = int(xy[1])
  302. else:
  303. raise parser.error("position option must be of form XxY (e.g. 50x64)")
  304. else:
  305. x = 0
  306. y = 0
  307.  
  308. if opts.size:
  309. if re.match("^[0-9]*x[0-9]*$", opts.size.strip()):
  310. size = opts.size.strip().split("x")
  311. width = int(size[0])
  312. height = int(size[1])
  313. else:
  314. raise parser.error("size option must be of form HxW (e.g. 1280x720)")
  315. else:
  316. width = dres[0]
  317. height = dres[1]
  318.  
  319. # Calculate cropping
  320. width -= opts.crop_left + opts.crop_right
  321. height -= opts.crop_top + opts.crop_bottom
  322. x += opts.crop_left
  323. y += opts.crop_top
  324.  
  325. # Make sure the capture resolution conforms to the restrictions
  326. # of the video codec. Crop to conform, if necessary.
  327. mults = {"h264": 2, "h264_fast": 2, "mpeg4": 2, "dirac": 2, "xvid": 2, "theora": 8, "huffyuv": 2, "vp8": 1}
  328. width -= width % mults[opts.vcodec]
  329. height -= height % mults[opts.vcodec]
  330.  
  331. # Verify that capture area is on screen
  332. if (x + width) > dres[0] or (y + height) > dres[1]:
  333. parser.error("specified capture area is off screen.")
  334.  
  335. # Capture!
  336. if not opts.no_audio:
  337. proc = subprocess.Popen(capture_line(fps, x, y, width, height, opts.display_device, opts.audio_device, opts.vcodec, opts.acodec, out_path)).wait()
  338. else:
  339. proc = subprocess.Popen(video_capture_line(fps, x, y, width, height, opts.display_device, opts.vcodec, out_path)).wait()
  340.  
  341. print("Done!")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement