record-screen.rs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. extern crate docopt;
  2. extern crate quest;
  3. extern crate repng;
  4. extern crate scrap;
  5. extern crate serde;
  6. extern crate webm;
  7. use std::fs::{File, OpenOptions};
  8. use std::path::PathBuf;
  9. use std::sync::atomic::{AtomicBool, Ordering};
  10. use std::sync::Arc;
  11. use std::time::{Duration, Instant};
  12. use std::{io, thread};
  13. use docopt::Docopt;
  14. use scrap::codec::{EncoderApi, EncoderCfg};
  15. use webm::mux;
  16. use webm::mux::Track;
  17. use scrap::vpxcodec as vpx_encode;
  18. use scrap::{Capturer, Display, TraitCapturer, STRIDE_ALIGN};
  19. const USAGE: &'static str = "
  20. Simple WebM screen capture.
  21. Usage:
  22. record-screen <path> [--time=<s>] [--fps=<fps>] [--quality=<quality>] [--ba=<kbps>] [--codec CODEC]
  23. record-screen (-h | --help)
  24. Options:
  25. -h --help Show this screen.
  26. --time=<s> Recording duration in seconds.
  27. --fps=<fps> Frames per second [default: 30].
  28. --quality=<quality> Video quality [default: 1.0].
  29. --ba=<kbps> Audio bitrate in kilobits per second [default: 96].
  30. --codec CODEC Configure the codec used. [default: vp9]
  31. Valid values: vp8, vp9.
  32. ";
  33. #[derive(Debug, serde::Deserialize)]
  34. struct Args {
  35. arg_path: PathBuf,
  36. flag_codec: Codec,
  37. flag_time: Option<u64>,
  38. flag_fps: u64,
  39. flag_quality: f32,
  40. }
  41. #[derive(Debug, serde::Deserialize)]
  42. enum Codec {
  43. Vp8,
  44. Vp9,
  45. }
  46. fn main() -> io::Result<()> {
  47. let args: Args = Docopt::new(USAGE)
  48. .and_then(|d| d.deserialize())
  49. .unwrap_or_else(|e| e.exit());
  50. let duration = args.flag_time.map(Duration::from_secs);
  51. let d = Display::primary().unwrap();
  52. let (width, height) = (d.width() as u32, d.height() as u32);
  53. // Setup the multiplexer.
  54. let out = match {
  55. OpenOptions::new()
  56. .write(true)
  57. .create_new(true)
  58. .open(&args.arg_path)
  59. } {
  60. Ok(file) => file,
  61. Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => {
  62. if loop {
  63. quest::ask("Overwrite the existing file? [y/N] ");
  64. if let Some(b) = quest::yesno(false)? {
  65. break b;
  66. }
  67. } {
  68. File::create(&args.arg_path)?
  69. } else {
  70. return Ok(());
  71. }
  72. }
  73. Err(e) => return Err(e.into()),
  74. };
  75. let mut webm =
  76. mux::Segment::new(mux::Writer::new(out)).expect("Could not initialize the multiplexer.");
  77. let (vpx_codec, mux_codec) = match args.flag_codec {
  78. Codec::Vp8 => (vpx_encode::VpxVideoCodecId::VP8, mux::VideoCodecId::VP8),
  79. Codec::Vp9 => (vpx_encode::VpxVideoCodecId::VP9, mux::VideoCodecId::VP9),
  80. };
  81. let mut vt = webm.add_video_track(width, height, None, mux_codec);
  82. // Setup the encoder.
  83. let quality = args.flag_quality;
  84. let mut vpx = vpx_encode::VpxEncoder::new(
  85. EncoderCfg::VPX(vpx_encode::VpxEncoderConfig {
  86. width,
  87. height,
  88. quality,
  89. codec: vpx_codec,
  90. keyframe_interval: None,
  91. }),
  92. false,
  93. )
  94. .unwrap();
  95. // Start recording.
  96. let start = Instant::now();
  97. let stop = Arc::new(AtomicBool::new(false));
  98. thread::spawn({
  99. let stop = stop.clone();
  100. move || {
  101. let _ = quest::ask("Recording! Press ⏎ to stop.");
  102. let _ = quest::text();
  103. stop.store(true, Ordering::Release);
  104. }
  105. });
  106. let spf = Duration::from_nanos(1_000_000_000 / args.flag_fps);
  107. // Capturer object is expensive, avoiding to create it frequently.
  108. let mut c = Capturer::new(d).unwrap();
  109. let mut yuv = Vec::new();
  110. let mut mid_data = Vec::new();
  111. while !stop.load(Ordering::Acquire) {
  112. let now = Instant::now();
  113. let time = now - start;
  114. if Some(true) == duration.map(|d| time > d) {
  115. break;
  116. }
  117. if let Ok(frame) = c.frame(Duration::from_millis(0)) {
  118. let ms = time.as_secs() * 1000 + time.subsec_millis() as u64;
  119. frame.to(vpx.yuvfmt(), &mut yuv, &mut mid_data).unwrap();
  120. for frame in vpx.encode(ms as i64, &yuv, STRIDE_ALIGN).unwrap() {
  121. vt.add_frame(frame.data, frame.pts as u64 * 1_000_000, frame.key);
  122. }
  123. }
  124. let dt = now.elapsed();
  125. if dt < spf {
  126. thread::sleep(spf - dt);
  127. }
  128. }
  129. // End things.
  130. let _ = webm.finalize(None);
  131. Ok(())
  132. }