Last active
May 17, 2024 02:29
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************************* | |
* 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