record-screen.rs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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, Quality as Q};
  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: Balanced].
  29. Valid values: Best, Balanced, Low.
  30. --ba=<kbps> Audio bitrate in kilobits per second [default: 96].
  31. --codec CODEC Configure the codec used. [default: vp9]
  32. Valid values: vp8, vp9.
  33. ";
  34. #[derive(Debug, serde::Deserialize)]
  35. struct Args {
  36. arg_path: PathBuf,
  37. flag_codec: Codec,
  38. flag_time: Option<u64>,
  39. flag_fps: u64,
  40. flag_quality: Quality,
  41. }
  42. #[derive(Debug, serde::Deserialize)]
  43. enum Quality {
  44. Best,
  45. Balanced,
  46. Low,
  47. }
  48. #[derive(Debug, serde::Deserialize)]
  49. enum Codec {
  50. Vp8,
  51. Vp9,
  52. }
  53. fn main() -> io::Result<()> {
  54. let args: Args = Docopt::new(USAGE)
  55. .and_then(|d| d.deserialize())
  56. .unwrap_or_else(|e| e.exit());
  57. let duration = args.flag_time.map(Duration::from_secs);
  58. let d = Display::primary().unwrap();
  59. let (width, height) = (d.width() as u32, d.height() as u32);
  60. // Setup the multiplexer.
  61. let out = match {
  62. OpenOptions::new()
  63. .write(true)
  64. .create_new(true)
  65. .open(&args.arg_path)
  66. } {
  67. Ok(file) => file,
  68. Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => {
  69. if loop {
  70. quest::ask("Overwrite the existing file? [y/N] ");
  71. if let Some(b) = quest::yesno(false)? {
  72. break b;
  73. }
  74. } {
  75. File::create(&args.arg_path)?
  76. } else {
  77. return Ok(());
  78. }
  79. }
  80. Err(e) => return Err(e.into()),
  81. };
  82. let mut webm =
  83. mux::Segment::new(mux::Writer::new(out)).expect("Could not initialize the multiplexer.");
  84. let (vpx_codec, mux_codec) = match args.flag_codec {
  85. Codec::Vp8 => (vpx_encode::VpxVideoCodecId::VP8, mux::VideoCodecId::VP8),
  86. Codec::Vp9 => (vpx_encode::VpxVideoCodecId::VP9, mux::VideoCodecId::VP9),
  87. };
  88. let mut vt = webm.add_video_track(width, height, None, mux_codec);
  89. // Setup the encoder.
  90. let quality = match args.flag_quality {
  91. Quality::Best => Q::Best,
  92. Quality::Balanced => Q::Balanced,
  93. Quality::Low => Q::Low,
  94. };
  95. let mut vpx = vpx_encode::VpxEncoder::new(
  96. EncoderCfg::VPX(vpx_encode::VpxEncoderConfig {
  97. width,
  98. height,
  99. quality,
  100. codec: vpx_codec,
  101. keyframe_interval: None,
  102. }),
  103. false,
  104. )
  105. .unwrap();
  106. // Start recording.
  107. let start = Instant::now();
  108. let stop = Arc::new(AtomicBool::new(false));
  109. thread::spawn({
  110. let stop = stop.clone();
  111. move || {
  112. let _ = quest::ask("Recording! Press ⏎ to stop.");
  113. let _ = quest::text();
  114. stop.store(true, Ordering::Release);
  115. }
  116. });
  117. let spf = Duration::from_nanos(1_000_000_000 / args.flag_fps);
  118. // Capturer object is expensive, avoiding to create it frequently.
  119. let mut c = Capturer::new(d).unwrap();
  120. let mut yuv = Vec::new();
  121. let mut mid_data = Vec::new();
  122. while !stop.load(Ordering::Acquire) {
  123. let now = Instant::now();
  124. let time = now - start;
  125. if Some(true) == duration.map(|d| time > d) {
  126. break;
  127. }
  128. if let Ok(frame) = c.frame(Duration::from_millis(0)) {
  129. let ms = time.as_secs() * 1000 + time.subsec_millis() as u64;
  130. frame.to(vpx.yuvfmt(), &mut yuv, &mut mid_data).unwrap();
  131. for frame in vpx.encode(ms as i64, &yuv, STRIDE_ALIGN).unwrap() {
  132. vt.add_frame(frame.data, frame.pts as u64 * 1_000_000, frame.key);
  133. }
  134. }
  135. let dt = now.elapsed();
  136. if dt < spf {
  137. thread::sleep(spf - dt);
  138. }
  139. }
  140. // End things.
  141. let _ = webm.finalize(None);
  142. Ok(())
  143. }