Skip to content

Instantly share code, notes, and snippets.

@kmark
Last active May 17, 2024 02:29
Show Gist options
  • Save kmark/3549b2a035f65f6f1911 to your computer and use it in GitHub Desktop.
Save kmark/3549b2a035f65f6f1911 to your computer and use it in GitHub Desktop.
A Rust port of a PHP utility. https://gist.github.com/kmark/6959258
/*******************************************************************************
* Copyright © 2014 Kevin Mark *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
* You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
*******************************************************************************/
#![feature(phase)]
#[phase(plugin)]
extern crate regex_macros;
extern crate regex;
extern crate getopts;
extern crate rustc;
use std::os;
use std::io;
use std::io::fs;
use regex::Regex;
fn main() {
let args = os::args();
let program = args[0].as_slice(); // Name used to invoke program
// Define all runtime options
let opts = [
getopts::optopt("b", "bitrate", "minimum bit rate in Kbps", "BITRATE"),
getopts::optopt("m", "mediainfo", "path to mediainfo binary", "PATH"),
getopts::optopt("l", "lame", "skips lame-encoded files with given regex", "REGEX"),
getopts::optflag("o", "outenc", "output lame settings of all low quality files"),
getopts::optflag("s", "short", "truncate repetitive directory output"),
getopts::optflag("h", "help", "display this message"),
getopts::optflag("v", "version", "output version and compiler information")
];
// Fail on any invalid arguments
let matches = match getopts::getopts(args.tail(), opts) {
Ok(m) => m,
Err(f) => {
fail!("Invalid options\n{}", f);
}
};
// Print version or help info and exit
if matches.opt_present("version") {
println!("findLowQualitySongs v1.0 - Finds low quality songs in a directory based on a minimum bit rate.");
// Removed!
//println!("Built by rustc {}", rustc::front::std_inject::VERSION);
return
}
else if matches.opt_present("help") {
println!("Usage: {program} [options] directory [...]
{options}",
program = program,
options = getopts::usage("This utility recursively searches all given directories for low quality audio files.
What constitutes \"low quality\" is up to you. The default minimum bit rate is
256 Kbps but this can be adjusted with the -b option. If MediaInfo is not in your
PATH, you will need to manually specify its location with -m. The --lame regex
option is especially useful for filtering out VBR MP3s which may dip below
your minimum. For instance -l \"-V ?0\" will suppress all V0 LAME MP3s.
Supported file extensions: AAC AIFF FLAC M4A M4P MP3 MP4 OGG WAV WMA", opts));
}
// Bitrate defaults to 256, convert it to an int so we can compare it
let bitrate = match from_str::<int>(match matches.opt_str("b") {
Some(b) => b,
None => "256".to_string()
}.as_slice()) {
Some(b) => b,
None => fail!("Supplied bitrate is not a number.")
};
// Specify path to mediainfo, default hopes that the program is in the path
let mediainfo = match matches.opt_str("m") {
Some(m) => m,
None => "mediainfo".to_string()
};
// Boolean options
let short = matches.opt_present("s");
let outenc = matches.opt_present("o");
// Compile regex used to parse file names and MediaInfo output
let extRegex = regex!(r"^(?i:mp4|m4a|aac|mp3|aiff|wav|flac|ogg|m4p|wma)$");
let miRegex = regex!(r"(?im:^Overall bit rate +: ([\d.]+) Kbps)");
let encRegex = regex!(r"(?im:^Encoding settings +: (-.+)$)");
let useLameRegex = matches.opt_str("l").is_some();
// Fail on bad user-supplied regex
let lameRegex = match Regex::new(match matches.opt_str("l") {
Some(l) => l,
None => ".*".to_string()
}.as_slice()) {
Ok(r) => r,
Err(e) => fail!("LAME regex error: {}", e)
};
// Loop over every free option which we assume is a directory path
for dir in matches.free.iter() {
let path = Path::new(dir.as_slice());
// Loop over every "file" in the directory
for file in fs::walk_dir(&path).unwrap() {
// Skip anything that's not an actual file
let ftype = match file.stat() {
Ok(s) => s.kind,
Err(e) => fail!("Failed to retrieve file statistics: {}", e)
};
if ftype != io::TypeFile {
continue;
}
let filestr = match file.as_str() {
Some(s) => s,
None => fail!("Failed to obtain a file path, that's odd.")
};
// Ignore files that do not match specific audio extensions
if !extRegex.is_match(match file.extension_str() {
Some(ext) => ext,
None => continue
}) {
continue;
}
// Create the MediaInfo process
let mut miProcess = match io::Command::new(mediainfo.clone()).arg(filestr).spawn() {
Ok(p) => p,
Err(e) => fail!("Failed to execute MediaInfo: {}", e)
};
// Pull the final output from the process
let output = match String::from_utf8(miProcess.stdout.get_mut_ref().read_to_end().unwrap()) {
Ok(o) => o,
Err(e) => fail!("Failed to obtain MediaInfo output: {}", e)
};
// Get the bit rate from the MediaInfo scan, continue on if this doesn't work
let fileBitrate = match from_str::<int>(match miRegex.captures(output.as_slice()) {
Some(b) => b.at(1),
None => continue
}) {
Some(b) => b,
None => continue
};
// If we fail to meet our target bit rate
if fileBitrate < bitrate {
let displayFilePath = if short {
// Crude yet apparently-effective method of truncating reptitive output
filestr.as_slice().slice_from(
path.as_str().unwrap().to_string().len() + 1).to_string()
} else {
file.as_str().unwrap().to_string()
};
let encSettingsCap = encRegex.captures(output.as_slice());
if encSettingsCap.is_some() {
let encSettings = encSettingsCap.unwrap().at(1);
// File matches the user-specified lame regex, ignore it
if useLameRegex && lameRegex.is_match(encSettings) {
continue;
}
if outenc {
// Output the LAME encoding settings too
println!("{} Kbps : {} [{}]", fileBitrate, displayFilePath, encSettings);
continue;
}
}
println!("{} Kbps : {}", fileBitrate, displayFilePath);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment