poor automove impl
This commit is contained in:
+208
-56
@@ -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()
|
||||
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 => SubInstant(0f32)
|
||||
};
|
||||
|
||||
let from_index = match argv.value_of("fromindex") {
|
||||
Some(s) => {
|
||||
s.parse().expect("Bad --from-index format")
|
||||
}
|
||||
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::<AutoMoveTag>::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<AutoMoveTag>,
|
||||
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<SubInstant>,
|
||||
renumber_i : u32,
|
||||
}
|
||||
|
||||
fn transform_subtitles<'a>(lines : &mut Box<dyn Iterator<Item=Result<String, io::Error>> + 'a>,
|
||||
@@ -226,9 +305,7 @@ fn transform_subtitles<'a>(lines : &mut Box<dyn Iterator<Item=Result<String, io:
|
||||
mut opts : TransformOpts) {
|
||||
debug!("Opts: {:#?}", opts);
|
||||
|
||||
let mut start_time = SubInstant(0f32);
|
||||
let mut first_found = false;
|
||||
let mut renumber_i: u32 = 0;
|
||||
let mut istate = IterState::default();
|
||||
|
||||
let mut linebuf = vec![];
|
||||
while let Some(Ok(x)) = lines.next() {
|
||||
@@ -242,19 +319,21 @@ fn transform_subtitles<'a>(lines : &mut Box<dyn Iterator<Item=Result<String, io:
|
||||
continue;
|
||||
}
|
||||
|
||||
let istate_backup = istate;
|
||||
|
||||
// 236
|
||||
// 00:18:01,755 --> 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<dyn Iterator<Item=Result<String, io:
|
||||
linebuf.push(x);
|
||||
}
|
||||
|
||||
let num_new = if opts.renumber {
|
||||
istate.renumber_i += 1;
|
||||
istate.renumber_i
|
||||
} else {
|
||||
num_orig
|
||||
};
|
||||
|
||||
// advance numbering only for the really emitted entries
|
||||
|
||||
|
||||
let mut subtitle = Subtitle {
|
||||
num,
|
||||
start,
|
||||
dur: SubDuration(end.0 - start.0),
|
||||
num : num_new,
|
||||
start: sub_start,
|
||||
dur: sub_end - sub_start,
|
||||
text: linebuf.join("\n"),
|
||||
};
|
||||
|
||||
if start >= 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<dyn Iterator<Item=Result<String, io:
|
||||
}
|
||||
|
||||
if opts.scale != 1f32 {
|
||||
subtitle.start = subtitle.start.scale(start_time, opts.scale);
|
||||
subtitle.start = subtitle.start.scale(istate.start_time.unwrap(), opts.scale);
|
||||
}
|
||||
|
||||
subtitle.dur *= opts.durscale;
|
||||
subtitle.start += opts.shift;
|
||||
|
||||
for amove in opts.automove.iter_mut() {
|
||||
match amove {
|
||||
AutoMoveTag::ByIndex(idx, ref vidt) => {
|
||||
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<SubDuration> for SubInstant {
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<SubInstant> for SubInstant {
|
||||
type Output = SubDuration;
|
||||
|
||||
fn sub(self, rhs: SubInstant) -> Self::Output {
|
||||
SubDuration(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for SubDuration {
|
||||
type Output = SubDuration;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user