Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preview generation #10

Merged
merged 11 commits into from
Aug 27, 2023
23 changes: 23 additions & 0 deletions .github/workflows/builld.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: "Build"

on:
push:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: "RustTest"
name: "RustServiceTest"

on:
pull_request:
Expand Down Expand Up @@ -32,6 +32,9 @@ jobs:
- name: "Check out the repo"
uses: actions/checkout@v3

- name: "Install ffmpeg"
run: sudo apt install -y ffmpeg

- uses: "actions-rs/toolchain@v1"
with:
profile: "minimal"
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile.movies-db-service
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM ubuntu:22.04

RUN apt update && apt upgrade -y && apt install -y curl
RUN apt update && apt upgrade -y && apt install -y curl ffmpeg

COPY movies-db-service/target/release/movies-db-cli /opt/movies-db/bin/movies-db-cli
COPY docker/service/start_movie_service.sh /opt/movies-db/bin/start_movie_service.sh
Expand Down
3 changes: 2 additions & 1 deletion movies-db-service/movies-db-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ edition = "2021"
movies-db = { path = "../movies-db" }
log = "0.4"
anyhow = "1.0"
simple-logging = "2.0"
env_logger = "0.10"
chrono = { version = "0.4" }
clap = { version = "4.2", features = ["derive"] }
actix-web = "4"
17 changes: 16 additions & 1 deletion movies-db-service/movies-db-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use clap::Parser;

use log::LevelFilter;

use std::io::Write;

/// Parses the program arguments and returns None, if no arguments were provided and Some otherwise.
fn parse_args() -> Result<Options> {
let options = Options::parse();
Expand All @@ -20,7 +22,20 @@ fn parse_args() -> Result<Options> {

/// Initializes the program logging
fn initialize_logging(filter: LevelFilter) {
simple_logging::log_to(std::io::stdout(), filter);
env_logger::Builder::new()
.format(|buf, record| {
writeln!(
buf,
"{}:{} {} [{}] - {}",
record.file().unwrap_or("unknown"),
record.line().unwrap_or(0),
chrono::Local::now().format("%Y-%m-%dT%H:%M:%S"),
record.level(),
record.args()
)
})
.filter_level(filter)
.init();
}

/// Runs the program.
Expand Down
7 changes: 6 additions & 1 deletion movies-db-service/movies-db-cli/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,24 @@ pub struct Options {
pub log_level: LogLevel,

/// The address to bind the http server to
#[arg(short, value_enum, long, default_value = "0.0.0.0:3030")]
#[arg(short, long, default_value = "0.0.0.0:3030")]
pub address: String,

/// The path to the root directory
#[arg(short, long)]
pub root_dir: PathBuf,

/// The path to where ffmpeg and ffprobe are located
#[arg(short, long, default_value = "/usr/bin/")]
pub ffmpeg: PathBuf,
}

impl From<Options> for ServiceOptions {
fn from(options: Options) -> Self {
ServiceOptions {
root_dir: options.root_dir,
http_address: options.address.parse().unwrap(),
ffmpeg: options.ffmpeg,
}
}
}
4 changes: 4 additions & 0 deletions movies-db-service/movies-db/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ pub struct Options {

/// The address to bind the HTTP server to.
pub http_address: SocketAddr,

/// The path to where ffmpeg and ffprobe are located
pub ffmpeg: PathBuf,
}

impl Default for Options {
fn default() -> Self {
Self {
root_dir: PathBuf::from("./"),
http_address: SocketAddr::from(([127, 0, 0, 1], 3030)),
ffmpeg: PathBuf::from("/usr/bin/"),
}
}
}
177 changes: 177 additions & 0 deletions movies-db-service/movies-db/src/service/ffmpeg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
use std::path::{Path, PathBuf};

use log::{info, trace};
use tokio::process::Command;

use crate::Error;

pub struct FFMpeg {
ffmpeg_bin_path: PathBuf,
ffprobe_bin_path: PathBuf,
}

/// creates and returns the path to the ffmpeg binary.
///
/// # Arguments
/// * `root_dir` - The path to the directory where ffmpeg is located.
fn create_ffmpeg_bin_path(ffmpeg_dir: &Path) -> PathBuf {
let mut ffmpeg_bin_path = ffmpeg_dir.to_path_buf();
ffmpeg_bin_path.push("ffmpeg");

ffmpeg_bin_path
}

/// creates and returns the path to the ffprobe binary.
///
/// # Arguments
/// * `ffprobe_dir` - The path to the directory where ffprobe is located.
fn create_ffprobe_bin_path(ffprobe_dir: &Path) -> PathBuf {
let mut ffprobe_bin_path = ffprobe_dir.to_path_buf();
ffprobe_bin_path.push("ffprobe");

ffprobe_bin_path
}

impl FFMpeg {
/// Creates a new instance of ffmpeg.
///
/// # Arguments
/// * `root_dir` - The path to the directory where the ffmpeg and ffprobe binaries are located.
pub async fn new(root_dir: &Path) -> Result<Self, Error> {
let ffmpeg_bin_path = create_ffmpeg_bin_path(root_dir);
let ffprobe_bin_path = create_ffprobe_bin_path(root_dir);

Self::check_bin(&ffmpeg_bin_path, "ffmpeg").await?;
Self::check_bin(&ffmpeg_bin_path, "ffprobe").await?;

Ok(Self {
ffmpeg_bin_path,
ffprobe_bin_path,
})
}

/// Retur ns the duration of the given movie file in seconds.
pub async fn get_movie_duration(&self, movie_file: &Path) -> Result<f64, Error> {
trace!("get_movie_duration: movie_file={}", movie_file.display());
let output = Command::new(&self.ffprobe_bin_path)
.arg("-v")
.arg("error")
.arg("-show_entries")
.arg("format=duration")
.arg("-of")
.arg("default=noprint_wrappers=1:nokey=1")
.arg(movie_file)
.output()
.await
.map_err(|e| {
Error::Internal(format!(
"Failed to execute ffprobe binary '{}': {}",
self.ffprobe_bin_path.display(),
e
))
})?;

if !output.status.success() {
return Err(Error::Internal(format!(
"Failed to execute ffprobe binary '{}': {}",
self.ffprobe_bin_path.display(),
String::from_utf8_lossy(&output.stderr)
)));
}

let duration = String::from_utf8_lossy(&output.stdout)
.trim()
.parse::<f64>()
.map_err(|e| {
Error::Internal(format!(
"Failed to parse ffprobe output '{}': {}",
String::from_utf8_lossy(&output.stdout),
e
))
})?;

Ok(duration)
}

/// Creates a screenshot of the given movie file at the given timestamp.
///
/// # Arguments
/// * `movie_file` - The path to the movie file.
/// * `timestamp` - The timestamp in seconds at which to create the screenshot.
pub async fn create_screenshot(
&self,
movie_file: &Path,
timestamp: f64,
) -> Result<Vec<u8>, Error> {
// trigger ffmpeg to create a screenshot of the given movie file at the given timestamp
// and return the screenshot data in png format
let output = Command::new(&self.ffmpeg_bin_path)
.arg("-ss")
.arg(timestamp.to_string())
.arg("-i")
.arg(movie_file)
.arg("-vframes")
.arg("1")
.arg("-q:v")
.arg("2")
.arg("-c:v")
.arg("png")
.arg("-f")
.arg("image2pipe")
.arg("-")
.output()
.await
.map_err(|e| {
Error::Internal(format!(
"Failed to execute ffmpeg binary '{}': {}",
self.ffmpeg_bin_path.display(),
e
))
})?;

if !output.status.success() {
return Err(Error::Internal(format!(
"Failed to execute ffmpeg binary '{}': {}",
self.ffmpeg_bin_path.display(),
String::from_utf8_lossy(&output.stderr)
)));
}

Ok(output.stdout)
}

/// Checks either ffmpeg or ffprobe binary.
///
/// # Arguments
/// * `bin` - The path to the binary to check.
/// * `name` - The name of the binary to check.
async fn check_bin(bin: &Path, name: &str) -> Result<(), Error> {
let output = Command::new(bin)
.arg("-version")
.output()
.await
.map_err(|e| {
Error::Internal(format!(
"Failed to execute ffmpeg binary '{}': {}",
bin.display(),
e
))
})?;

if !output.status.success() {
return Err(Error::Internal(format!(
"Failed to execute ffmpeg binary '{}': {}",
bin.display(),
String::from_utf8_lossy(&output.stderr)
)));
}

// extract first line of version info
let output = String::from_utf8_lossy(&output.stdout);
let output = output.lines().next().unwrap_or_default();

info!("{} Version Info: {}", name, output);

Ok(())
}
}
2 changes: 2 additions & 0 deletions movies-db-service/movies-db/src/service/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub mod ffmpeg;
mod preview_generator;
mod service_handler;
mod service_impl;

Expand Down
Loading
Loading