Skip to content

Commit

Permalink
Version 1.0.0
Browse files Browse the repository at this point in the history
* Add options --filter-video and --filter-sub to be able to only include
  subs/videos that match a given regular expression.
* Rust 2021
  • Loading branch information
kl committed Jan 23, 2022
1 parent 7429365 commit 90800c2
Show file tree
Hide file tree
Showing 14 changed files with 234 additions and 115 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
Sun Jan 22 2022

Version 1.0.0

* Add options --filter-video and --filter-sub to be able to only include
subs/videos that match a given regular expression.
* Rust 2021

Sat Oct 21 2021

Version 0.4.1
Expand Down
26 changes: 13 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
edition = "2018"
edition = "2021"
name = "sub-batch"
version = "0.4.1"
version = "1.0.0"
authors = ["Kalle Lindström <[email protected]>"]
description = "Match and rename subtitle files to video files and perfom other batch operations on subtitle files."
homepage = "https://github.com/kl/sub-batch"
Expand Down
39 changes: 30 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
Match and rename subtitle files to video files and perfom other batch operations on subtitle files.

## Install

### Precompiled binaries (Linux and Windows)
https://github.com/kl/sub-batch/releases

### Nix package (credit to: https://github.com/erictapen)
https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/video/sub-batch/default.nix

### Cargo
```cargo install sub-batch```

## Usage
Expand All @@ -15,17 +23,21 @@ FLAGS:
-V, --version Prints version information
OPTIONS:
-p, --path <path> The path to look for subs in. [default: .]
-s, --filter-sub <filter_sub> A regular expression which, if given, must match the file name of a subtitle
for that subtitle to be targeted by any of the SUBCOMMANDS.
-v, --filter-video <filter_video> A regular expression which, if given, must match the file name of a video file
for that video file to be targeted by any of the SUBCOMMANDS.
-p, --path <path> The path to look for subs/videos in. [default: .]
SUBCOMMANDS:
alass Adjusts the timing of all subs that are matched with a video file using `alass`
(https://github.com/kaegi/alass). This can automatically fix wrong timings due to commercial breaks
for example.
help Prints this message or the help of the given subcommand(s)
rename Renames subtitle files to match the corresponding video file
time Adjusts the timing of all subs. The value is specified in milliseconds, and can be negative
time-mpv Adjusts the timing of all subs interactively using mpv. `mpv` must be installed on the system for
this command to work.
alass Adjusts the timing of all subs that are matched with a video file using `alass`
(https://github.com/kaegi/alass). This can automatically fix wrong timings due to commercial breaks
for example.
help Prints this message or the help of the given subcommand(s)
rename Renames subtitle files to match the corresponding video file.
time Adjusts the timing of all subs. The value is specified in milliseconds, and can be negative.
time-mpv Adjusts the timing of all subs interactively using mpv. `mpv` must be installed on the system for
this command to work.
```
## Renaming subtitle files to match their corresponding video file
Put the subs and the videos in the same directory, for example:
Expand Down Expand Up @@ -77,6 +89,15 @@ sub-batch time -50
```
which moves all subtitles back by 50 ms.

## Targeting only certain subtiles/videos

You can give a regex to filter the subs/videos that should be included when running any of the subcommands.
For example, to only change timings of subtiles in the target directory that has "SUB" in the filename:
```
sub-batch --filter-sub SUB time -50
```
Any other subtiles files in the target directory are ignored. Video files can be filtered the same way with the ```--filter-video``` option.

## Adjusting subtitle timings interactively with `mpv`

If mpv (https://mpv.io) is installed sub-batch can use `mpv` to adjust timings interactively and have the updated subtitles auto-refresh in mpv. To enter this mode run:
Expand Down
14 changes: 8 additions & 6 deletions src/commands/alass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ impl AlassCommand {
pub fn run(&self) -> AnyResult<()> {
let alass_binary = alass_binary()?;

let matches = scanner::scan(ScanOptions::new(
&self.global_conf.path,
self.conf.sub_area.clone(),
self.conf.video_area.clone(),
))?;
let matches = scanner::scan(ScanOptions {
path: &self.global_conf.path,
sub_area: self.conf.sub_area.as_ref(),
video_area: self.conf.video_area.as_ref(),
sub_filter: self.global_conf.sub_filter.as_ref(),
video_filter: self.global_conf.video_filter.as_ref(),
})?;

if matches.is_empty() {
return Ok(());
Expand All @@ -40,7 +42,7 @@ impl AlassCommand {
} else {
matches
.par_iter()
.try_for_each(|m| self.align(&alass_binary, &m))?;
.try_for_each(|m| self.align(&alass_binary, m))?;
}
}

Expand Down
8 changes: 7 additions & 1 deletion src/commands/mpv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,13 @@ impl MpvCommand {
}

fn first_sub_video_match(&self) -> AnyResult<SubAndFile> {
let mut matches = scanner::scan(ScanOptions::path_only(&self.global_conf.path))?;
let mut matches = scanner::scan(ScanOptions {
path: &self.global_conf.path,
sub_filter: self.global_conf.sub_filter.as_ref(),
video_filter: self.global_conf.video_filter.as_ref(),
sub_area: None,
video_area: None,
})?;
if matches.is_empty() {
bail!("must have at least one matching video/subtitle file pair");
}
Expand Down
12 changes: 7 additions & 5 deletions src/commands/rename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ use anyhow::Result as AnyResult;
use std::fs;

pub fn run(global_conf: &GlobalConfig, conf: RenameConfig) -> AnyResult<()> {
let matches = scanner::scan(ScanOptions::new(
&global_conf.path,
conf.sub_area,
conf.video_area,
))?;
let matches = scanner::scan(ScanOptions {
path: &global_conf.path,
sub_area: conf.sub_area.as_ref(),
video_area: conf.video_area.as_ref(),
sub_filter: global_conf.sub_filter.as_ref(),
video_filter: global_conf.video_filter.as_ref(),
})?;

let renames: Vec<SubAndFile> = matches
.into_iter()
Expand Down
8 changes: 7 additions & 1 deletion src/commands/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ use subparse::timetypes::TimeDelta;
use subparse::SubtitleFile;

pub fn run(global_conf: &GlobalConfig, conf: TimeConfig) -> AnyResult<()> {
let matches = scanner::scan_subs_only(ScanOptions::path_only(&global_conf.path))?;
let matches = scanner::scan_subs_only(ScanOptions {
path: &global_conf.path,
sub_filter: global_conf.sub_filter.as_ref(),
video_filter: global_conf.video_filter.as_ref(),
sub_area: None,
video_area: None,
})?;

let mut parsed_subs: Vec<SubtitleFile> = matches
.iter()
Expand Down
52 changes: 37 additions & 15 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use std::str::FromStr;
pub struct GlobalConfig {
pub path: PathBuf,
pub no_confirm: bool,
pub sub_filter: Option<Regex>,
pub video_filter: Option<Regex>,
}

#[derive(Debug)]
Expand Down Expand Up @@ -66,16 +68,34 @@ impl GlobalConfig {
.takes_value(true)
.global(true)
.default_value(".")
.help("The path to look for subs in."),
.help("The path to look for subs/videos in."),
)
.arg(
Arg::with_name("no_confirm")
.long("no-confirm")
.short("y")
.takes_value(false)
.help(
"If this flag is set sub-batch will not ask for any confirmation before \
applying operations.",
"Only videos with file names that match this regex will be targeted by \
any of the SUBCOMMANDS.",
),
)
.arg(Arg::with_name("filter_sub")
.long("filter-sub")
.short("s")
.takes_value(true)
.help(
"Only subtiles with file names that match this regex will be targeted by \
any of the SUBCOMMANDS.",
),
)
.arg(Arg::with_name("filter_video")
.long("filter-video")
.short("v")
.takes_value(true)
.help(
"A regular expression which, if given, must match the file name of a video file \
for that video file to be targeted by any of the SUBCOMMANDS.",
),
)
.subcommand(
Expand All @@ -89,7 +109,7 @@ impl GlobalConfig {
.allow_hyphen_values(true)
.help(
"Specifies a regular expression that defines the part of the video \
filename where episode number should be extracted from.",
filename where the episode number should be extracted from.",
),
)
.arg(
Expand All @@ -100,7 +120,7 @@ impl GlobalConfig {
.allow_hyphen_values(true)
.help(
"Specifies a regular expression that defines the part of the \
subtitle filename where episode number should be extracted from.",
subtitle filename where the episode number should be extracted from.",
),
)
)
Expand Down Expand Up @@ -197,23 +217,23 @@ impl GlobalConfig {

let command_config = match subcommand_name {
"rename" => CommandConfig::Rename(RenameConfig {
video_area: area(&subcommand_matches, "video_area")?,
sub_area: area(&subcommand_matches, "sub_area")?,
video_area: regex_arg(subcommand_matches, "video_area")?,
sub_area: regex_arg(subcommand_matches, "sub_area")?,
}),
"time" => {
let mut tc = TimeConfig::timing(timing(&subcommand_matches)?);
if let Some(encoding) = encoding(&subcommand_matches) {
let mut tc = TimeConfig::timing(timing(subcommand_matches)?);
if let Some(encoding) = encoding(subcommand_matches) {
tc.encoding = encoding?;
}
if let Some(fps) = fps(&subcommand_matches) {
if let Some(fps) = fps(subcommand_matches) {
tc.fps = fps?;
}
CommandConfig::Time(tc)
}
"alass" => CommandConfig::Alass(AlassConfig {
flags: alass_flags(&subcommand_matches),
video_area: area(&subcommand_matches, "video_area")?,
sub_area: area(&subcommand_matches, "sub_area")?,
flags: alass_flags(subcommand_matches),
video_area: regex_arg(subcommand_matches, "video_area")?,
sub_area: regex_arg(subcommand_matches, "sub_area")?,
no_parallel: subcommand_matches.is_present("no_parallel"),
}),
"time-mpv" => CommandConfig::Mpv,
Expand All @@ -224,6 +244,8 @@ impl GlobalConfig {
GlobalConfig {
path: matches.value_of("path").unwrap().into(),
no_confirm: matches.is_present("no_confirm"),
sub_filter: regex_arg(&matches, "filter_sub")?,
video_filter: regex_arg(&matches, "filter_video")?,
},
command_config,
))
Expand All @@ -240,7 +262,7 @@ fn check_args<'a>(matches: &'a ArgMatches) -> (&'a str, &'a ArgMatches<'a>) {
}
}

fn area(matches: &ArgMatches, key: &str) -> AnyResult<Option<Regex>> {
fn regex_arg(matches: &ArgMatches, key: &str) -> AnyResult<Option<Regex>> {
Ok(if let Some(v) = matches.value_of(key) {
Some(Regex::new(v)?)
} else {
Expand All @@ -260,7 +282,7 @@ fn encoding(matches: &ArgMatches) -> Option<AnyResult<&'static Encoding>> {
}

fn fps(matches: &ArgMatches) -> Option<Result<f64, ParseFloatError>> {
matches.value_of("fps").map(|v| f64::from_str(v))
matches.value_of("fps").map(f64::from_str)
}

fn alass_flags(matches: &ArgMatches) -> Vec<String> {
Expand Down
Loading

0 comments on commit 90800c2

Please sign in to comment.