h265_transcode/
h265_transcode.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
use std::{io::Write, path::Path, thread};

use ffmpeg_sidecar::{
  command::FfmpegCommand,
  event::{FfmpegEvent, LogLevel},
};

/// 1. Read an H265 source video from file
/// 2. Decode video
/// 3. Composite with an overlay image rendered by Rust
/// 4. Re-encode back to H265 to file
///
/// ```console
/// cargo run --example h265_transcode
/// ```
fn main() {
  // Create an H265 source video as a starting point
  let input_path = "output/h265.mp4";
  if !Path::new(input_path).exists() {
    create_h265_source(input_path);
  }

  // One instance decodes H265 to raw frames
  let mut input = FfmpegCommand::new()
    .input(input_path)
    .rawvideo()
    .spawn()
    .unwrap();

  // Frames can be transformed by Iterator `.map()`.
  // This example is a no-op, with frames passed through unaltered.
  let transformed_frames = input.iter().unwrap().filter_frames();

  // You could easily add some "middleware" processing here:
  // - overlay or composite another RGB image (or even another Ffmpeg Iterator)
  // - apply a filter like blur or convolution
  // Note: some of these operations are also possible with FFmpeg's (somewhat arcane)
  // `filtergraph` API, but doing it in Rust gives you much finer-grained
  // control, debuggability, and modularity -- you can pull in any Rust crate
  // you need.

  // A second instance encodes the updated frames back to H265
  let mut output = FfmpegCommand::new()
    .args([
      "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", "600x800", "-r", "30",
    ]) // note: should be possible to infer these params from the source input stream
    .input("-")
    .args(["-c:v", "libx265"])
    .args(["-y", "output/h265_overlay.mp4"])
    .spawn()
    .unwrap();

  // Connect the two instances
  let mut stdin = output.take_stdin().unwrap();
  thread::spawn(move || {
    // `for_each` blocks through the end of the iterator,
    // so we run it in another thread.
    transformed_frames.for_each(|f| {
      stdin.write_all(&f.data).ok();
    });
  });

  // On the main thread, run the output instance to completion
  output.iter().unwrap().for_each(|e| match e {
    FfmpegEvent::Log(LogLevel::Error, e) => println!("Error: {}", e),
    FfmpegEvent::Progress(p) => println!("Progress: {} / 00:00:15", p.time),
    _ => {}
  });
}

/// Create a H265 source video from scratch
fn create_h265_source(path_str: &str) {
  println!("Creating H265 source video: {}", path_str);
  FfmpegCommand::new()
    .args("-f lavfi -i testsrc=size=600x800:rate=30:duration=15 -c:v libx265".split(' '))
    .arg(path_str)
    .spawn()
    .unwrap()
    .iter()
    .unwrap()
    .for_each(|e| match e {
      FfmpegEvent::Log(LogLevel::Error, e) => println!("Error: {}", e),
      FfmpegEvent::Progress(p) => println!("Progress: {} / 00:00:15", p.time),
      _ => {}
    });
  println!("Created H265 source video: {}", path_str);
}