Skip to content

Commit

Permalink
video2srt: new crate
Browse files Browse the repository at this point in the history
  • Loading branch information
astraw committed Aug 4, 2024
1 parent 962570d commit 46f768c
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ members = [
"write-debian-changelog",
"zip-or-dir",
"zip-or-dir/dir2zip",
"media-utils/video2srt",
]

exclude = ["led-box-firmware", "led-box-firmware-pico"]
Expand Down
19 changes: 19 additions & 0 deletions media-utils/video2srt/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "video2srt"
description = "Create a subtitle .srt file from video with Strand Cam timestamps"
version = "0.1.0"
edition = "2021"

[dependencies]
clap = { version = "4.3.4", features = ["derive"] }
color-eyre = "0.6.2"

tracing = "0.1.40"
chrono = { version = "0.4.38", features = [
"libc",
"serde",
"std",
], default-features = false }

env-tracing-logger = { path = "../../env-tracing-logger" }
frame-source = { path = "../frame-source" }
115 changes: 115 additions & 0 deletions media-utils/video2srt/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use clap::Parser;
use color_eyre::eyre::{self, WrapErr};
use std::{io::Write, path::PathBuf};

use frame_source::Timestamp;

#[derive(Debug, Parser)]
#[command(version, about)]
struct Opt {
/// Input video filename.
#[arg(short, long)]
input: PathBuf,

/// Output srt filename. Defaults to "<INPUT>.srt"
#[arg(short, long)]
output: Option<PathBuf>,
}

trait Srt {
fn srt(&self) -> String;
}

impl Srt for std::time::Duration {
fn srt(&self) -> String {
// from https://en.wikipedia.org/wiki/SubRip :
// "hours:minutes:seconds,milliseconds with time units fixed to two
// zero-padded digits and fractions fixed to three zero-padded digits
// (00:00:00,000). The fractional separator used is the comma, since the
// program was written in France."
let total_secs = self.as_secs();
let hours = total_secs / (60 * 60);
let minutes = (total_secs % (60 * 60)) / 60;
let seconds = total_secs % 60;
dbg!(total_secs);
dbg!(hours);
dbg!(minutes);
dbg!(seconds);
debug_assert_eq!(total_secs, hours * 60 * 60 + minutes * 60 + seconds);
let millis = self.subsec_millis();
format!("{hours:02}:{minutes:02}:{seconds:02},{millis:03}")
}
}

fn main() -> eyre::Result<()> {
if std::env::var_os("RUST_LOG").is_none() {
std::env::set_var("RUST_LOG", "info");
}
env_tracing_logger::init();
let opt = Opt::parse();

let output = opt.output;

let output = output.unwrap_or_else(|| {
let mut output = opt.input.as_os_str().to_owned();
output.push(".srt");
output.into()
});

let do_decode_h264 = false;
let mut src = frame_source::from_path(&opt.input, do_decode_h264)
.with_context(|| format!("while opening path {}", opt.input.display()))?;

let start_time = src
.frame0_time()
.ok_or_else(|| eyre::eyre!("no start time found"))?;

let mut out_fd = std::fs::File::create(&output)?;

let mut prev_data: Option<(std::time::Duration, _)> = None;

let mut count = 1;
for (_fno, frame) in src.iter().enumerate() {
let frame = frame?;
let pts = match frame.timestamp() {
Timestamp::Duration(pts) => pts,
_ => {
eyre::bail!(
"video has no PTS timestamps and framerate was not \
specified on the command line."
);
}
};
let frame_stamp = start_time + pts;
if let Some((prev_pts, prev_stamp)) = prev_data.take() {
out_fd.write_all(
format!(
"{}\n{} --> {}\n{}\n\n",
count,
prev_pts.srt(),
pts.srt(),
prev_stamp
)
.as_bytes(),
)?;
count += 1;
}
prev_data = Some((pts, frame_stamp));
}

if let Some((prev_pts, prev_stamp)) = prev_data.take() {
let pts = prev_pts + std::time::Duration::from_secs(1);
out_fd.write_all(
format!(
"{}\n{} --> {}\n{}\n\n",
count,
prev_pts.srt(),
pts.srt(),
prev_stamp
)
.as_bytes(),
)?;
}

Ok(())
}

0 comments on commit 46f768c

Please sign in to comment.