Advertisement
BSDG33KCLUB

sc.py py-script ffmpeg screencast for openbsd 5.5

Mar 27th, 2014
111
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.00 KB | None | 0 0
  1. #!/usr/bin/env python
  2. #usage: python sc.py in console
  3. ## OpenBSD 5.5 Screencasting script for ffmpeg ##
  4. ## Python script created by: Gary Perreault (c) 2012 ##
  5.  
  6. # Easy-to-change defaults for users
  7. DEFAULT_FPS = 10
  8. DEFAULT_FILE_EXTENSION = ".avi"
  9. ACCEPTABLE_FILE_EXTENSIONS = [".avi", ".mp4", ".mov", ".mkv", ".ogv"]
  10. DEFAULT_CAPTURE_AUDIO_DEVICE = "/dev/audio1"
  11. DEFAULT_CAPTURE_DISPLAY_DEVICE = "+0+0"
  12. DEFAULT_AUDIO_CODEC = "mp3"
  13. DEFAULT_VIDEO_CODEC = "mpeg4"
  14.  
  15. import os
  16. import sys
  17. import os.path
  18. import glob
  19. import time
  20. import random
  21. import tempfile
  22. import optparse
  23. import subprocess
  24. import re
  25.  
  26.  
  27. PYTHON_3 = (sys.version_info[0] == 3)
  28.  
  29.  
  30. # Optional packages
  31. try:
  32. import Tkinter
  33. have_tk = True
  34. except ImportError:
  35. have_tk = False
  36.  
  37. try:
  38. import multiprocessing
  39. have_multiproc = True
  40. except ImportError:
  41. have_multiproc = False
  42.  
  43.  
  44. # Video codec lines
  45. vcodecs = {}
  46. vcodecs["h264"] = ["-vcodec", "libx264", "-preset", "medium"]
  47. vcodecs["h264_fast"] = ["-vcodec", "libx264", "-preset", "ultrafast", "-g", "15", "-crf", "0", "-pix_fmt", "yuv444p"]
  48. vcodecs["mpeg4"] = ["-vcodec", "mpeg4", "-qmax", "1", "-qmin", "1"]
  49. #vcodecs["xvid"] = ["-vcodec", "libxvid", "-b", "40000kb"]
  50. vcodecs["huffyuv"] = ["-vcodec", "huffyuv"]
  51. vcodecs["vp8"] = ["-vcodec", "libvpx", "-qmax", "2", "-qmin", "1"]
  52. vcodecs["theora"] = ["-vcodec", "libtheora", "-b", "40000kb"]
  53. #vcodecs["dirac"] = ["-vcodec", "libschroedinger", "-b", "40000kb"]
  54.  
  55. # Audio codec lines
  56. acodecs = {}
  57. acodecs["pcm"] = ["-acodec", "pcm_s16le"]
  58. #acodecs["flac"] = ["-acodec", "flac"]
  59. acodecs["vorbis"] = ["-acodec", "libvorbis", "-ab", "320k"]
  60. acodecs["mp3"] = ["-acodec", "libmp3lame", "-ab", "320k"]
  61. acodecs["aac"] = ["-acodec", "libfdk-aac", "-ab", "320k"]
  62.  
  63.  
  64. def capture_line(fps, x, y, height, width, display_device, audio_device, video_codec, audio_codec, output_path):
  65. """ Returns the command line to capture video+audio, in a list form
  66. compatible with Popen.
  67. """
  68. threads = 2
  69. if have_multiproc:
  70. # Detect the number of threads we have available
  71. threads = multiprocessing.cpu_count()
  72. line = ["ffmpeg",
  73. "-f", "s16le",
  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 = ["ffmpeg",
  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", "s16le",
  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