fixed some logic and added autoscaling option
This commit is contained in:
Generated
+1
-1
@@ -264,7 +264,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "srtune"
|
name = "srtune"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|||||||
+1
-2
@@ -1,11 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "srtune"
|
name = "srtune"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
|
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
#srt = "0.1.0"
|
|
||||||
clap = "2.33.0"
|
clap = "2.33.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
failure = "0.1.5"
|
failure = "0.1.5"
|
||||||
|
|||||||
+313
-89
@@ -10,8 +10,9 @@ use std::io;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
use std::ops::{Add, Mul, MulAssign, AddAssign};
|
use std::ops::{Add, Mul, MulAssign, AddAssign, SubAssign, Sub};
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
|
use serde::export::fmt::Debug;
|
||||||
|
|
||||||
const LOG_LEVELS: [&str; 5] = ["error", "warn", "info", "debug", "trace"];
|
const LOG_LEVELS: [&str; 5] = ["error", "warn", "info", "debug", "trace"];
|
||||||
const SPAMMY_LIBS: [&str; 5] = ["tokio_reactor", "hyper", "reqwest", "mio", "want"];
|
const SPAMMY_LIBS: [&str; 5] = ["tokio_reactor", "hyper", "reqwest", "mio", "want"];
|
||||||
@@ -22,7 +23,7 @@ fn main() {
|
|||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.arg(clap::Arg::with_name("input")
|
.arg(clap::Arg::with_name("input")
|
||||||
.value_name("INFILE")
|
.value_name("INFILE")
|
||||||
.help("Input file, -- for stdin"),
|
.help("Input file, leave out for stdin"),
|
||||||
)
|
)
|
||||||
.arg(clap::Arg::with_name("output")
|
.arg(clap::Arg::with_name("output")
|
||||||
.short("o")
|
.short("o")
|
||||||
@@ -30,29 +31,52 @@ fn main() {
|
|||||||
.value_name("OUTFILE")
|
.value_name("OUTFILE")
|
||||||
.help("Output file, defaults to stdout"),
|
.help("Output file, defaults to stdout"),
|
||||||
)
|
)
|
||||||
.arg(clap::Arg::with_name("from")
|
.arg(clap::Arg::with_name("fromtime")
|
||||||
.short("f")
|
.short("f")
|
||||||
.long("from")
|
.long("from-time")
|
||||||
.value_name("FROM")
|
.value_name("TIME")
|
||||||
.help("Index of the first affected entry, defaults to 0. With '--renumber', the original numbers are used."),
|
.help("Time of the first affected entry, defaults to 0. Use seconds, M:S or \
|
||||||
|
H:M:S, decimals are supported. Uses the original times before any shifts or \
|
||||||
|
scaling."),
|
||||||
|
)
|
||||||
|
.arg(clap::Arg::with_name("fromindex")
|
||||||
|
.short("F")
|
||||||
|
.long("from-index")
|
||||||
|
.value_name("INDEX")
|
||||||
|
.help("Time of the first affected entry, defaults to 0. Use seconds, M:S or \
|
||||||
|
H:M:S, decimals are supported. Uses the original times before any shifts or \
|
||||||
|
scaling."),
|
||||||
)
|
)
|
||||||
.arg(clap::Arg::with_name("move")
|
.arg(clap::Arg::with_name("move")
|
||||||
.short("m")
|
.short("m")
|
||||||
.long("move")
|
.long("move")
|
||||||
.value_name("SECS")
|
.value_name("TIME")
|
||||||
.help("Time shift, accepts positive or negative float"),
|
.help("Move subtitles in time. Use seconds, M:S or H:M:S, decimals and minus \
|
||||||
|
are supported."),
|
||||||
)
|
)
|
||||||
.arg(clap::Arg::with_name("scale")
|
.arg(clap::Arg::with_name("scale")
|
||||||
.short("s")
|
.short("s")
|
||||||
.long("scale")
|
.long("scale")
|
||||||
.value_name("RATIO")
|
.value_name("RATIO")
|
||||||
.help("Scale subtitle times and durations to compensate for bitrate differences.\nIf a start time was given, the scaling is relative to this point, otherwise to the first subtitle in the file."),
|
.help("Scale subtitle times and durations to compensate for bitrate \
|
||||||
|
differences. 1 means identity, 1.1 makes all times 10% longer. If a start \
|
||||||
|
time was given, the scaling is relative to this point, otherwise to the first \
|
||||||
|
subtitle in the file. Has no effect if '--autoscale' is used."),
|
||||||
|
)
|
||||||
|
.arg(clap::Arg::with_name("autoscale")
|
||||||
|
.short("S")
|
||||||
|
.long("autoscale")
|
||||||
|
.value_name("SUBTIME=VIDEOTIME")
|
||||||
|
.help("Calculate scaling based on a perceived difference. The scaling is \
|
||||||
|
related to the first subtitle, so ensure it is aligned properly with '--move'."),
|
||||||
)
|
)
|
||||||
.arg(clap::Arg::with_name("durscale")
|
.arg(clap::Arg::with_name("durscale")
|
||||||
.short("d")
|
.short("d")
|
||||||
.long("durscale")
|
.long("durscale")
|
||||||
.value_name("RATIO")
|
.value_name("RATIO")
|
||||||
.help("Scale durations, can be combined with '--scale'"),
|
.help("Scale durations, can be combined with '--scale' or '--autoscale'. The \
|
||||||
|
given value will always be multiplied by the absolute time scale. 1 means \
|
||||||
|
identity, 1.1 makes all times 10% longer."),
|
||||||
)
|
)
|
||||||
.arg(clap::Arg::with_name("renumber")
|
.arg(clap::Arg::with_name("renumber")
|
||||||
.short("r")
|
.short("r")
|
||||||
@@ -93,12 +117,62 @@ fn main() {
|
|||||||
}
|
}
|
||||||
builder.init();
|
builder.init();
|
||||||
|
|
||||||
|
let from_time = match argv.value_of("fromtime") {
|
||||||
|
Some(s) => {
|
||||||
|
SubDuration::try_from(s).expect("Bad --from-time format").as_instant()
|
||||||
|
}
|
||||||
|
None => SubInstant(0f32)
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_index = match argv.value_of("fromindex") {
|
||||||
|
Some(s) => {
|
||||||
|
s.parse().expect("Bad --from-index format")
|
||||||
|
}
|
||||||
|
None => 0u32
|
||||||
|
};
|
||||||
|
|
||||||
|
let shift = match argv.value_of("move") {
|
||||||
|
Some(s) => {
|
||||||
|
SubDuration::try_from(s).expect("Bad --move format")
|
||||||
|
}
|
||||||
|
None => SubDuration(0f32)
|
||||||
|
};
|
||||||
|
|
||||||
|
let scale = match argv.value_of("scale") {
|
||||||
|
Some(s) => {
|
||||||
|
s.parse().expect("Bad --scale format")
|
||||||
|
}
|
||||||
|
None => 1f32
|
||||||
|
};
|
||||||
|
|
||||||
|
let durscale = match argv.value_of("durscale") {
|
||||||
|
Some(s) => {
|
||||||
|
s.parse().expect("Bad --durscale format")
|
||||||
|
}
|
||||||
|
None => 1f32
|
||||||
|
}; // always also shrink durations
|
||||||
|
|
||||||
|
let autoscale = match argv.value_of("autoscale") {
|
||||||
|
Some(s) => {
|
||||||
|
let halves : Vec<&str> = s.split("=").collect();
|
||||||
|
if halves.len() != 2 {
|
||||||
|
panic!("Bad --autoscale format, should be SUBTIME=VIDEOTIME")
|
||||||
|
}
|
||||||
|
let (first, second) = (halves[0], halves[1]);
|
||||||
|
let subtime = SubDuration::try_from(first).expect("Bad --autoscale format").as_instant();
|
||||||
|
let vidtime = SubDuration::try_from(second).expect("Bad --autoscale format").as_instant();
|
||||||
|
|
||||||
|
Some((subtime, vidtime))
|
||||||
|
}
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
|
||||||
let inf = argv.value_of("input");
|
let inf = argv.value_of("input");
|
||||||
let outf = argv.value_of("output");
|
let outf = argv.value_of("output");
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
let stdout = io::stdout();
|
let stdout = io::stdout();
|
||||||
|
|
||||||
let mut iter: Box<dyn Iterator<Item=Result<String, io::Error>>> = match inf {
|
let mut lines_iterator: Box<dyn Iterator<Item=Result<String, io::Error>>> = match inf {
|
||||||
None => {
|
None => {
|
||||||
Box::new(stdin.lock().lines())
|
Box::new(stdin.lock().lines())
|
||||||
}
|
}
|
||||||
@@ -108,7 +182,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut out : Box<dyn Write> = match outf {
|
let mut outfile: Box<dyn Write> = match outf {
|
||||||
None => {
|
None => {
|
||||||
Box::new(stdout.lock())
|
Box::new(stdout.lock())
|
||||||
}
|
}
|
||||||
@@ -123,44 +197,41 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let from_s = match argv.value_of("from") {
|
|
||||||
Some(s) => {
|
|
||||||
s.parse().expect("Bad --from format")
|
|
||||||
},
|
|
||||||
None => 0u32
|
|
||||||
};
|
|
||||||
|
|
||||||
let move_s = match argv.value_of("move") {
|
|
||||||
Some(s) => {
|
|
||||||
s.parse().expect("Bad --move format")
|
|
||||||
},
|
|
||||||
None => 0f32
|
|
||||||
};
|
|
||||||
|
|
||||||
let scale_s = match argv.value_of("scale") {
|
|
||||||
Some(s) => {
|
|
||||||
s.parse().expect("Bad --scale format")
|
|
||||||
},
|
|
||||||
None => 1f32
|
|
||||||
};
|
|
||||||
|
|
||||||
let durscale_s = match argv.value_of("durscale") {
|
|
||||||
Some(s) => {
|
|
||||||
s.parse().expect("Bad --durscale format")
|
|
||||||
},
|
|
||||||
None => 1f32
|
|
||||||
} * scale_s; // always also shrink durations
|
|
||||||
|
|
||||||
let renumber = argv.is_present("renumber");
|
let renumber = argv.is_present("renumber");
|
||||||
|
|
||||||
info!("Opts: from #{}, move {}s, scale {}x, durscale {}x", from_s, move_s, scale_s, durscale_s);
|
transform_subtitles(&mut lines_iterator, &mut outfile, TransformOpts {
|
||||||
|
renumber,
|
||||||
|
autoscale,
|
||||||
|
durscale,
|
||||||
|
scale,
|
||||||
|
shift,
|
||||||
|
from_index,
|
||||||
|
from_time,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let mut scale_start = SubTime(0f32);
|
#[derive(Debug)]
|
||||||
|
struct TransformOpts {
|
||||||
|
renumber: bool,
|
||||||
|
autoscale: Option<(SubInstant, SubInstant)>,
|
||||||
|
durscale: f32,
|
||||||
|
scale: f32,
|
||||||
|
shift: SubDuration,
|
||||||
|
from_index: u32,
|
||||||
|
from_time: SubInstant,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_subtitles<'a>(lines : &mut Box<dyn Iterator<Item=Result<String, io::Error>> + 'a>,
|
||||||
|
outfile : &mut Box<dyn Write + 'a>,
|
||||||
|
mut opts : TransformOpts) {
|
||||||
|
debug!("Opts: {:#?}", opts);
|
||||||
|
|
||||||
|
let mut start_time = SubInstant(0f32);
|
||||||
let mut first_found = false;
|
let mut first_found = false;
|
||||||
let mut renumber_i : u32 = 0;
|
let mut renumber_i: u32 = 0;
|
||||||
|
|
||||||
let mut text = vec![];
|
let mut linebuf = vec![];
|
||||||
while let Some(Ok(x)) = iter.next() {
|
while let Some(Ok(x)) = lines.next() {
|
||||||
let mut x = x.trim();
|
let mut x = x.trim();
|
||||||
if x.starts_with('\u{feff}') {
|
if x.starts_with('\u{feff}') {
|
||||||
debug!("Stripping BOM mark");
|
debug!("Stripping BOM mark");
|
||||||
@@ -178,55 +249,75 @@ fn main() {
|
|||||||
match u32::from_str(x) {
|
match u32::from_str(x) {
|
||||||
Ok(num) => {
|
Ok(num) => {
|
||||||
// println!("Entry {}", num);
|
// println!("Entry {}", num);
|
||||||
let datesrow = iter.next().unwrap().unwrap();
|
let datesrow = lines.next().unwrap().unwrap();
|
||||||
if datesrow.contains(" --> ") {
|
if datesrow.contains(" --> ") {
|
||||||
let mut halves = datesrow.split(" --> ");
|
let mut halves = datesrow.split(" --> ");
|
||||||
let (first, second) = (halves.next().unwrap(), halves.next().unwrap());
|
let (first, second) = (halves.next().unwrap(), halves.next().unwrap());
|
||||||
let start = SubTime::try_from(first).unwrap();
|
let start = SubInstant::try_from(first).unwrap();
|
||||||
let end = SubTime::try_from(second).unwrap();
|
let end = SubInstant::try_from(second).unwrap();
|
||||||
|
|
||||||
text.clear();
|
linebuf.clear();
|
||||||
while let Some(Ok(x)) = iter.next() {
|
while let Some(Ok(x)) = lines.next() {
|
||||||
if x.is_empty() {
|
if x.is_empty() {
|
||||||
break; // space between the entries
|
break; // space between the entries
|
||||||
}
|
}
|
||||||
text.push(x);
|
linebuf.push(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut one = Subtitle {
|
let mut subtitle = Subtitle {
|
||||||
num,
|
num,
|
||||||
start,
|
start,
|
||||||
dur: SubDuration(end.0 - start.0),
|
dur: SubDuration(end.0 - start.0),
|
||||||
text: text.join("\n"),
|
text: linebuf.join("\n"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if num >= from_s {
|
if start >= opts.from_time && num >= opts.from_index {
|
||||||
if !first_found {
|
if !first_found {
|
||||||
debug!("Scaling anchored at {} (#{}), start edits", start, num);
|
debug!("Scaling anchored at {} (#{}), editing starts", start, num);
|
||||||
scale_start = start;
|
debug!("Shifting by: {}", opts.shift);
|
||||||
|
|
||||||
|
start_time = start;
|
||||||
first_found = true;
|
first_found = true;
|
||||||
|
|
||||||
|
if let Some((mut subt, mut vidt)) = opts.autoscale {
|
||||||
|
debug!("Autoscale: VT {} -> ST {}", vidt, subt);
|
||||||
|
subt -= start_time;
|
||||||
|
vidt -= start_time + opts.shift;
|
||||||
|
if subt.0 <= 0f32 {
|
||||||
|
panic!("Error in autoscale, start time is negative or zero.");
|
||||||
|
}
|
||||||
|
if vidt.0 <= 0f32 {
|
||||||
|
panic!("Error in autoscale, end time is negative or zero.");
|
||||||
|
}
|
||||||
|
debug!(" relative to #{}, after \"move\": VT {} -> ST {}", num, vidt, subt);
|
||||||
|
opts.scale = vidt.0 / subt.0;
|
||||||
|
debug!("Resolved scale as {}", opts.scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.durscale *= opts.scale;
|
||||||
|
debug!("Duration scaling is {}", opts.durscale);
|
||||||
}
|
}
|
||||||
|
|
||||||
if scale_s != 1f32 {
|
if opts.scale != 1f32 {
|
||||||
one.start = one.start.scale(scale_start, scale_s);
|
subtitle.start = subtitle.start.scale(start_time, opts.scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
one.dur *= durscale_s;
|
subtitle.dur *= opts.durscale;
|
||||||
one.start += move_s;
|
subtitle.start += opts.shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
if one.start.0 < 0f32 {
|
if subtitle.start.0 < 0f32 {
|
||||||
warn!("Discarding negative time entry #{} @ {:.3}s", one.num, one.start.0);
|
warn!("Discarding negative time entry #{} @ {:.3}s", subtitle.num, subtitle.start.0);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// advance numbering only for the really emitted entries
|
// advance numbering only for the really emitted entries
|
||||||
if renumber {
|
if opts.renumber {
|
||||||
renumber_i += 1;
|
renumber_i += 1;
|
||||||
one.num = renumber_i;
|
subtitle.num = renumber_i;
|
||||||
}
|
}
|
||||||
|
|
||||||
out.write(one.to_string().as_bytes()).expect("failed to write");
|
outfile.write(subtitle.to_string().as_bytes()).expect("failed to write");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -239,20 +330,46 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out.flush().unwrap();
|
outfile.flush().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, PartialEq, PartialOrd)]
|
||||||
struct SubTime(f32);
|
struct SubInstant(f32);
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
impl Debug for SubInstant {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
write!(f, "Time({})", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, PartialOrd)]
|
||||||
struct SubDuration(f32);
|
struct SubDuration(f32);
|
||||||
|
|
||||||
impl Add<SubDuration> for SubTime {
|
impl Debug for SubDuration {
|
||||||
type Output = SubTime;
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
write!(f, "Duration({})", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SubDuration {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
write!(f, "{}", SubInstant(self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<SubDuration> for SubInstant {
|
||||||
|
type Output = SubInstant;
|
||||||
|
|
||||||
fn add(self, rhs: SubDuration) -> Self::Output {
|
fn add(self, rhs: SubDuration) -> Self::Output {
|
||||||
SubTime(self.0 + rhs.0)
|
SubInstant(self.0 + rhs.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub<SubDuration> for SubInstant {
|
||||||
|
type Output = SubInstant;
|
||||||
|
|
||||||
|
fn sub(self, rhs: SubDuration) -> Self::Output {
|
||||||
|
SubInstant(self.0 - rhs.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,36 +393,63 @@ impl AddAssign<f32> for SubDuration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubTime {
|
impl SubInstant {
|
||||||
/// Scale by a factor with a custom start time
|
/// Scale by a factor with a custom start time
|
||||||
pub fn scale(&self, start : SubTime, factor : f32) -> SubTime {
|
pub fn scale(&self, start: SubInstant, factor: f32) -> SubInstant {
|
||||||
SubTime(start.0 + (self.0 - start.0) * factor)
|
SubInstant(start.0 + (self.0 - start.0) * factor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddAssign<f32> for SubTime {
|
impl AddAssign<f32> for SubInstant {
|
||||||
fn add_assign(&mut self, rhs: f32) {
|
fn add_assign(&mut self, rhs: f32) {
|
||||||
self.0 += rhs;
|
self.0 += rhs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for SubTime {
|
impl AddAssign<SubInstant> for SubInstant {
|
||||||
|
fn add_assign(&mut self, rhs: SubInstant) {
|
||||||
|
self.0 += rhs.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubAssign<SubInstant> for SubInstant {
|
||||||
|
fn sub_assign(&mut self, rhs: SubInstant) {
|
||||||
|
self.0 -= rhs.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubAssign<SubDuration> for SubInstant {
|
||||||
|
fn sub_assign(&mut self, rhs: SubDuration) {
|
||||||
|
self.0 -= rhs.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign<SubDuration> for SubInstant {
|
||||||
|
fn add_assign(&mut self, rhs: SubDuration) {
|
||||||
|
self.0 += rhs.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SubInstant {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
// TODO optimize this
|
// TODO optimize this
|
||||||
let mut secs = self.0;
|
let sign = self.0.signum();
|
||||||
|
let mut secs = self.0.abs();
|
||||||
let hours = (secs / 3600f32).floor();
|
let hours = (secs / 3600f32).floor();
|
||||||
secs -= hours * 3600f32;
|
secs -= hours * 3600f32;
|
||||||
let minutes = (secs / 60f32).floor();
|
let minutes = (secs / 60f32).floor();
|
||||||
secs -= minutes * 60f32;
|
secs -= minutes * 60f32;
|
||||||
let msecs = ((secs % 1f32)*1000f32).round();
|
let msecs = ((secs % 1f32) * 1000f32).round();
|
||||||
write!(f, "{:02}:{:02}:{:02},{:03}", hours, minutes, secs.floor(), msecs)
|
write!(f, "{}{:02}:{:02}:{:02},{:03}",
|
||||||
|
if sign.is_sign_negative() { "-" } else { "" },
|
||||||
|
hours, minutes, secs.floor(), msecs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Subtitle {
|
struct Subtitle {
|
||||||
num: u32,
|
num: u32,
|
||||||
start: SubTime,
|
start: SubInstant,
|
||||||
dur: SubDuration,
|
dur: SubDuration,
|
||||||
text: String,
|
text: String,
|
||||||
}
|
}
|
||||||
@@ -320,23 +464,103 @@ impl Display for Subtitle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref DATE_RE: Regex = Regex::new(r"(\d+):(\d+):(\d+),(\d+)").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&str> for SubTime {
|
impl TryFrom<&str> for SubInstant {
|
||||||
type Error = failure::Error;
|
type Error = failure::Error;
|
||||||
|
|
||||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
lazy_static! {
|
||||||
|
static ref DATE_RE: Regex = Regex::new(r"^(-)?(?P<h>\d+):(?P<m>\d+):(?P<s>\d+(:?[,.]\d+)?)$").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
match DATE_RE.captures(value) {
|
match DATE_RE.captures(value) {
|
||||||
Some(caps) => {
|
Some(caps) => {
|
||||||
Ok(SubTime(f32::from_str(caps.get(1).unwrap().as_str()).unwrap() * 3600f32 +
|
let minus = if caps.get(1).is_some() { -1f32 } else { 1f32 };
|
||||||
f32::from_str(caps.get(2).unwrap().as_str()).unwrap() * 60f32 +
|
|
||||||
f32::from_str(caps.get(3).unwrap().as_str()).unwrap() +
|
let h = &caps["h"];
|
||||||
f32::from_str(caps.get(4).unwrap().as_str()).unwrap() * 0.001f32))
|
let m = &caps["m"];
|
||||||
|
let s = caps["s"].replace(",", ".");
|
||||||
|
|
||||||
|
Ok(SubInstant(minus * (f32::from_str(h).unwrap() * 3600f32 +
|
||||||
|
f32::from_str(m).unwrap() * 60f32 +
|
||||||
|
f32::from_str(&s).unwrap())))
|
||||||
}
|
}
|
||||||
None => Err(failure::err_msg("Error parsing time."))
|
None => Err(failure::err_msg("Error parsing time."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SubDuration {
|
||||||
|
pub fn as_instant(&self) -> SubInstant {
|
||||||
|
SubInstant(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for SubDuration {
|
||||||
|
type Error = failure::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
lazy_static! {
|
||||||
|
static ref TIME_RE: Regex = Regex::new(r"^(-)?(?:(\d+):)?(?:(\d+):)?(\d+(?:[,.]\d+)?)$").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
match TIME_RE.captures(value) {
|
||||||
|
Some(caps) => {
|
||||||
|
let minus = if caps.get(1).is_some() { -1f32 } else { 1f32 };
|
||||||
|
let a = caps.get(2).map(|m| m.as_str()).unwrap_or("");
|
||||||
|
let b = caps.get(3).map(|m| m.as_str()).unwrap_or("");
|
||||||
|
let s = caps.get(4).map(|m| m.as_str()).unwrap_or("0").replace(",", ".");
|
||||||
|
|
||||||
|
let (h, m) = if b.is_empty() {
|
||||||
|
("0", if a.is_empty() { "0" } else { a })
|
||||||
|
} else {
|
||||||
|
(a, b)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(SubDuration(minus * (f32::from_str(h).unwrap() * 3600f32 +
|
||||||
|
f32::from_str(m).unwrap() * 60f32 +
|
||||||
|
f32::from_str(&s).unwrap())))
|
||||||
|
}
|
||||||
|
None => Err(failure::err_msg("Error parsing time: No match"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_duration() {
|
||||||
|
// this is used for user input on the command line
|
||||||
|
let bad = SubDuration(-1f32);
|
||||||
|
assert_eq!(SubDuration(45678f32), SubDuration::try_from("45678").unwrap_or(bad), "integer secs");
|
||||||
|
assert_eq!(SubDuration(1.23f32), SubDuration::try_from("1.23").unwrap_or(bad), "float secs with period");
|
||||||
|
assert_eq!(SubDuration(-1.23f32), SubDuration::try_from("-1.23").unwrap_or(bad), "MINUS float secs with period");
|
||||||
|
assert_eq!(SubDuration(1.23f32), SubDuration::try_from("1,23").unwrap_or(bad), "float secs with comma");
|
||||||
|
assert_eq!(SubDuration(121.15f32), SubDuration::try_from("2:1.15").unwrap_or(bad), "m:s.frac");
|
||||||
|
assert_eq!(SubDuration(121.15f32), SubDuration::try_from("2:01.15").unwrap_or(bad), "m:0s.frac");
|
||||||
|
assert_eq!(SubDuration(121.15f32), SubDuration::try_from("02:01.15").unwrap_or(bad), "0m:0s.frac");
|
||||||
|
assert_eq!(SubDuration(121.15f32), SubDuration::try_from("02:01,15").unwrap_or(bad), "0m:0s,frac");
|
||||||
|
|
||||||
|
assert_eq!(SubDuration(3721.15f32), SubDuration::try_from("1:02:01,15").unwrap_or(bad), "h:0m:0s,frac");
|
||||||
|
assert_eq!(SubDuration(3721.15f32), SubDuration::try_from("1:02:01,15").unwrap_or(bad), "h:0m:0s.frac");
|
||||||
|
assert_eq!(SubDuration(3721.15f32), SubDuration::try_from("01:02:01,15").unwrap_or(bad), "0h:0m:0s,frac");
|
||||||
|
assert_eq!(SubDuration(-3721.15f32), SubDuration::try_from("-01:02:01,15").unwrap_or(bad), "-0h:0m:0s,frac");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_instant() {
|
||||||
|
let bad = SubInstant(-1f32);
|
||||||
|
assert_eq!(SubInstant(1081.755f32), SubInstant::try_from("00:18:01,755").unwrap_or(bad));
|
||||||
|
assert_eq!(SubInstant(1081.755f32), SubInstant::try_from("00:18:01.755").unwrap_or(bad));
|
||||||
|
assert_eq!(SubInstant(1081.7f32), SubInstant::try_from("00:18:01.7").unwrap_or(bad));
|
||||||
|
assert_eq!(SubInstant(1081.7f32), SubInstant::try_from("0:18:1.7").unwrap_or(bad));
|
||||||
|
assert_eq!(SubInstant(0f32), SubInstant::try_from("00:00:00,000").unwrap_or(bad));
|
||||||
|
assert_eq!(SubInstant(-3600f32), SubInstant::try_from("-01:00:00,000").unwrap_or(bad));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stringify_instant() {
|
||||||
|
assert_eq!("00:18:01,755", SubInstant::try_from("00:18:01,755").unwrap().to_string());
|
||||||
|
assert_eq!("-00:18:01,755", SubInstant::try_from("-00:18:01,755").unwrap().to_string());
|
||||||
|
assert_eq!("-00:18:01,700", SubInstant::try_from("-00:18:01.7").unwrap().to_string());
|
||||||
|
assert_eq!("00:00:00,000", SubInstant::try_from("00:00:00,000").unwrap().to_string());
|
||||||
|
assert_eq!("-00:00:00,000", SubInstant::try_from("-00:00:00,000").unwrap().to_string());
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user