From 5285b61e9f27313af10ee5ce40e3d0bf9f9807ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sat, 22 Jun 2019 19:10:31 +0200 Subject: [PATCH] poor automove impl --- src/main.rs | 264 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 208 insertions(+), 56 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7c87c49..1a3123d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,18 @@ fn main() { let argv = clap::App::new("srtune") .version(env!("CARGO_PKG_VERSION")) + .about("Modify a .srt file to match a video. Input and output can be a file or stream, \ + so you pipe multiple invocations to create more complex operations. However, a single \ + invocation should suffice in most cases.\n\ + Times can be specified in any format: \ + seconds (400, 14.52), hours:minutes:seconds (14:00, 15:51.12, 1:30:00). Decimal point \ + can be period or comma; Times copied directly from the .srt file will also work.\n\ + When a command allows both time and index as a value, index must be prefixed with '@'.\ + The tool should be used iteratively, adjusting the invocation until the generated \ + subtitle file meets expectations. As such, times and indices accepted by its parameters \ + are, by default, the ones seen in the output file. Prefix a time or index with '^' \ + to use the original value from the input instead (i.e. original index 14 is '^@14') + ") .arg(clap::Arg::with_name("input") .value_name("INFILE") .help("Input file, leave out for stdin"), @@ -31,44 +43,43 @@ fn main() { .value_name("OUTFILE") .help("Output file, defaults to stdout"), ) - .arg(clap::Arg::with_name("fromtime") + .arg(clap::Arg::with_name("from") .short("f") .long("from-time") .value_name("TIME") - .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."), + .help("Time of the first affected entry, or its index. Defaults to 0/^@0."), ) .arg(clap::Arg::with_name("move") .short("m") .long("move") .value_name("TIME") .help("Move subtitles in time. Use seconds, M:S or H:M:S, decimals and minus \ - are supported."), + are supported. Starts at the point specified by '--from'"), + ) + .arg(clap::Arg::with_name("automove") + .short("M") + .long("automove") + .value_name("SUBTIME=VIDEOTIME") + .multiple(true) + .help("Move subtitles following a given time or index to match video times. \ + This automatically sets '--from' if not given explicitly."), ) .arg(clap::Arg::with_name("scale") .short("s") .long("scale") .value_name("RATIO") .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."), + differences. 1 means identity, 1.1 makes all times 10% longer. Scaling is \ + relative to the first emitted subtitle with positive time (after shifting). \ + 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'."), + related to the first emitted subtitle, so ensure it is aligned properly \ + with '--move'."), ) .arg(clap::Arg::with_name("durscale") .short("d") @@ -117,18 +128,27 @@ fn main() { } 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") + let from = match argv.value_of("from") { + Some(mut s) => { + if s.starts_with('^') { + s = &s[1..]; + if s.starts_with('@') { + let index = &s[1..].parse().expect("Bad --from format"); + FromTag::ByIndexOrig(*index) + } else { + // this is always the orig time + FromTag::ByTime(SubDuration::try_from(s).expect("Bad --from format").as_instant()) + } + } else { + if s.starts_with('@') { + let index = &s[1..].parse().expect("Bad --from format"); + FromTag::ByIndex(*index) + } else{ + FromTag::ByTime(SubDuration::try_from(s).expect("Bad --from format").as_instant()) + } + } } - None => 0u32 + None => FromTag::ByIndex(0) }; let shift = match argv.value_of("move") { @@ -159,6 +179,9 @@ fn main() { panic!("Bad --autoscale format, should be SUBTIME=VIDEOTIME") } let (first, second) = (halves[0], halves[1]); + if first.starts_with('^') { + panic!("'--autoscale' always uses original times"); + } let subtime = SubDuration::try_from(first).expect("Bad --autoscale format").as_instant(); let vidtime = SubDuration::try_from(second).expect("Bad --autoscale format").as_instant(); @@ -167,6 +190,40 @@ fn main() { None => None }; + let mut automove = Vec::::new(); + match argv.values_of("automove") { + Some(ss) => { + for s in ss { + let halves: Vec<&str> = s.split("=").collect(); + if halves.len() != 2 { + panic!("Bad --automove format, should be SUBTIME=VIDEOTIME") + } + let (mut first, second) = (halves[0], halves[1]); + let vidtime = SubDuration::try_from(second).expect("Bad --automove format").as_instant(); + + if first.starts_with('^') { + first = &first[1..]; + if s.starts_with('@') { + let index = &first[1..].parse().expect("Bad --automove format"); + automove.push(AutoMoveTag::ByIndexOrig(*index, vidtime)); + } else { + let subtime = SubDuration::try_from(first).expect("Bad --automove format").as_instant(); + automove.push(AutoMoveTag::ByTimeOrig(subtime, vidtime)); + } + } else { + if s.starts_with('@') { + let index = &first[1..].parse().expect("Bad --automove format"); + automove.push(AutoMoveTag::ByIndex(*index, vidtime)); + } else{ + let subtime = SubDuration::try_from(first).expect("Bad --automove format").as_instant(); + automove.push(AutoMoveTag::ByTime(subtime, vidtime)); + } + } + } + } + None => (/* no automoves */) + } + let inf = argv.value_of("input"); let outf = argv.value_of("output"); let stdin = io::stdin(); @@ -205,8 +262,8 @@ fn main() { durscale, scale, shift, - from_index, - from_time, + from, + automove, }); } @@ -217,8 +274,30 @@ struct TransformOpts { durscale: f32, scale: f32, shift: SubDuration, - from_index: u32, - from_time: SubInstant, + automove: Vec, + from: FromTag, +} + +#[derive(Debug)] +enum AutoMoveTag { + ByTime(SubInstant, SubInstant), + ByTimeOrig(SubInstant, SubInstant), + ByIndex(u32, SubInstant), + ByIndexOrig(u32, SubInstant), + ByIndexRelative(u32, SubDuration), +} + +#[derive(Debug)] +enum FromTag { + ByTime(SubInstant), + ByIndex(u32), + ByIndexOrig(u32) +} + +#[derive(Debug,Default,Clone,Copy)] +struct IterState { + start_time : Option, + renumber_i : u32, } fn transform_subtitles<'a>(lines : &mut Box> + 'a>, @@ -226,9 +305,7 @@ fn transform_subtitles<'a>(lines : &mut Box(lines : &mut Box 00:18:03,774 // (掃除機の音) // う~ん…。 match u32::from_str(x) { - Ok(num) => { + Ok(num_orig) => { // println!("Entry {}", num); let datesrow = lines.next().unwrap().unwrap(); if datesrow.contains(" --> ") { let mut halves = datesrow.split(" --> "); let (first, second) = (halves.next().unwrap(), halves.next().unwrap()); - let start = SubInstant::try_from(first).unwrap(); - let end = SubInstant::try_from(second).unwrap(); + let sub_start = SubInstant::try_from(first).unwrap(); + let sub_end = SubInstant::try_from(second).unwrap(); linebuf.clear(); while let Some(Ok(x)) = lines.next() { @@ -264,32 +343,45 @@ fn transform_subtitles<'a>(lines : &mut Box= opts.from_time && num >= opts.from_index { - if !first_found { - debug!("Scaling anchored at {} (#{}), editing starts", start, num); + if match opts.from { + FromTag::ByTime(ins) => ins <= sub_start, + FromTag::ByIndex(idx) => idx <= num_new, + FromTag::ByIndexOrig(idx) => idx <= num_orig, + } { + if istate.start_time.is_none() { + debug!("Scaling anchored at {} (#{}), editing starts", sub_start, num_orig); debug!("Shifting by: {}", opts.shift); - start_time = start; - first_found = true; + istate.start_time = Some(sub_start); if let Some((mut subt, mut vidt)) = opts.autoscale { debug!("Autoscale: VT {} -> ST {}", vidt, subt); - subt -= start_time; - vidt -= start_time + opts.shift; + subt -= sub_start; + vidt -= sub_start + 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); + debug!(" relative to #{}, after \"move\": VT {} -> ST {}", num_orig, vidt, subt); opts.scale = vidt.0 / subt.0; debug!("Resolved scale as {}", opts.scale); } @@ -299,24 +391,76 @@ fn transform_subtitles<'a>(lines : &mut Box { + if num_new >= *idx { + debug!("Move by new index starts, reached {}", idx); + let vidt = *vidt; + let dif = vidt - subtitle.start; + subtitle.start = vidt; + std::mem::replace(amove, AutoMoveTag::ByIndexRelative(num_new, dif)); + } else if *vidt < subtitle.start && *idx > num_new { +// istate = istate_backup; + warn!("Skip overlapped #{} (by index)", num_orig); + continue; + } + } + AutoMoveTag::ByIndexOrig(idx, ref vidt) => { + if num_orig >= *idx { + debug!("Move by orig index starts, reached {}", idx); + let vidt = *vidt; + let dif = vidt - subtitle.start; + subtitle.start = vidt; + std::mem::replace(amove, AutoMoveTag::ByIndexRelative(num_new, dif)); + } else if *vidt < sub_start && *idx > num_orig { +// istate = istate_backup; + warn!("Skip overlapped #{} (by orig index)", num_orig); + continue; + } + } + AutoMoveTag::ByTime(ref subt, ref vidt) => { + if subtitle.start >= *vidt { + // TODO verify + subtitle.start += *vidt - *subt; + } else if *vidt < subtitle.start && *subt > subtitle.start { +// istate = istate_backup; + warn!("Skip overlapped #{} (by time)", num_orig); + continue; + } + } + AutoMoveTag::ByTimeOrig(ref subt, ref vidt) => { + if sub_start >= *subt { + // TODO verify + subtitle.start += *vidt - *subt; + } else if *vidt < subtitle.start && *subt > sub_start { +// istate = istate_backup; + warn!("Skip overlapped #{} (by orig time)", num_orig); + continue; + } + }, + // this is used internally + AutoMoveTag::ByIndexRelative(ref idx, ref dif) => { + if num_new >= *idx { + subtitle.start += *dif; + } + }, + } + } } if subtitle.start.0 < 0f32 { warn!("Discarding negative time entry #{} @ {:.3}s", subtitle.num, subtitle.start.0); + istate = istate_backup; continue; } - // advance numbering only for the really emitted entries - if opts.renumber { - renumber_i += 1; - subtitle.num = renumber_i; - } - outfile.write(subtitle.to_string().as_bytes()).expect("failed to write"); } } @@ -373,6 +517,14 @@ impl Sub for SubInstant { } } +impl Sub for SubInstant { + type Output = SubDuration; + + fn sub(self, rhs: SubInstant) -> Self::Output { + SubDuration(self.0 - rhs.0) + } +} + impl Mul for SubDuration { type Output = SubDuration;