feat: if audiowaveform
is present, generate & cache peaks on backend
requires https://github.com/bbc/audiowaveform/ to be installed and on $PATH
This commit is contained in:
parent
b31ca05fdf
commit
9ea1eea3ea
2 changed files with 70 additions and 33 deletions
|
@ -14,41 +14,68 @@ const COLOR: &str = "#dc322f"; // solarized red
|
|||
|
||||
impl<'a> Previewable for AudioPath<'a> {
|
||||
fn get_thumbnail(&self, options: HashMap<String, String>) -> Result<Option<Vec<u8>>> {
|
||||
let outfile = tempfile::Builder::new().suffix(".webp").tempfile()?;
|
||||
match options.get("type").map(|x| x.as_str()) {
|
||||
Some("json") => {
|
||||
let outfile = tempfile::Builder::new().suffix(".json").tempfile()?;
|
||||
|
||||
let color = options
|
||||
.get("color")
|
||||
.map(String::to_owned)
|
||||
.unwrap_or_else(|| COLOR.into());
|
||||
let dimensions = options
|
||||
.get("dimensions")
|
||||
.map(String::to_owned)
|
||||
.unwrap_or_else(|| "860x256".into());
|
||||
// -i long_clip.mp3 -o long_clip.json --pixels-per-second 20 --bits 8
|
||||
let audiowaveform_cmd = Command::new("audiowaveform")
|
||||
.args(["-i", &self.0.to_string_lossy()])
|
||||
.args(["-o", &*outfile.path().to_string_lossy()])
|
||||
.args(["--pixels-per-second", "20"])
|
||||
.args(["--bits", "8"])
|
||||
.output()?;
|
||||
|
||||
let thumbnail_cmd = Command::new("ffmpeg")
|
||||
.args(["-i", &self.0.to_string_lossy()])
|
||||
.args([
|
||||
"-filter_complex",
|
||||
&format!(
|
||||
"[0:a]aformat=channel_layouts=mono, compand=gain=-2,
|
||||
showwavespic=s={dimensions}:colors={color},
|
||||
drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=1:color={color}"
|
||||
),
|
||||
])
|
||||
.args(["-vframes", "1"])
|
||||
.arg(&*outfile.path().to_string_lossy())
|
||||
.arg("-y")
|
||||
.output()?;
|
||||
if !audiowaveform_cmd.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to retrieve file duration: {:?}",
|
||||
String::from_utf8_lossy(&audiowaveform_cmd.stderr)
|
||||
));
|
||||
}
|
||||
|
||||
if !thumbnail_cmd.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to render thumbnail: {:?}",
|
||||
String::from_utf8_lossy(&thumbnail_cmd.stderr)
|
||||
));
|
||||
let mut buffer = Vec::new();
|
||||
outfile.as_file().read_to_end(&mut buffer)?;
|
||||
Ok(Some(buffer))
|
||||
}
|
||||
Some("image") | None => {
|
||||
let outfile = tempfile::Builder::new().suffix(".webp").tempfile()?;
|
||||
|
||||
let color = options
|
||||
.get("color")
|
||||
.map(String::to_owned)
|
||||
.unwrap_or_else(|| COLOR.into());
|
||||
let dimensions = options
|
||||
.get("dimensions")
|
||||
.map(String::to_owned)
|
||||
.unwrap_or_else(|| "860x256".into());
|
||||
|
||||
let thumbnail_cmd = Command::new("ffmpeg")
|
||||
.args(["-i", &self.0.to_string_lossy()])
|
||||
.args([
|
||||
"-filter_complex",
|
||||
&format!(
|
||||
"[0:a]aformat=channel_layouts=mono, compand=gain=-2,
|
||||
showwavespic=s={dimensions}:colors={color},
|
||||
drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=1:color={color}"
|
||||
),
|
||||
])
|
||||
.args(["-vframes", "1"])
|
||||
.arg(&*outfile.path().to_string_lossy())
|
||||
.arg("-y")
|
||||
.output()?;
|
||||
|
||||
if !thumbnail_cmd.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to render thumbnail: {:?}",
|
||||
String::from_utf8_lossy(&thumbnail_cmd.stderr)
|
||||
));
|
||||
}
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
outfile.as_file().read_to_end(&mut buffer)?;
|
||||
Ok(Some(buffer))
|
||||
}
|
||||
Some(_) => Err(anyhow!("type has to be one of: image, json")),
|
||||
}
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
outfile.as_file().read_to_end(&mut buffer)?;
|
||||
Ok(Some(buffer))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,6 +147,7 @@
|
|||
responsive: true,
|
||||
backend: "MediaElement",
|
||||
mediaControls: true,
|
||||
normalize: true,
|
||||
xhr: { cache: "force-cache" },
|
||||
plugins: [
|
||||
TimelinePlugin.default.create({
|
||||
|
@ -202,7 +203,16 @@
|
|||
setTimeout(() => wavesurfer.setCurrentTime(region.start));
|
||||
});
|
||||
|
||||
wavesurfer.load(`${API_URL}/raw/${address}`);
|
||||
try {
|
||||
const peaksReq = await fetch(`${API_URL}/thumb/${address}?type=json`);
|
||||
const peaks = await peaksReq.json();
|
||||
wavesurfer.load(`${API_URL}/raw/${address}`, peaks.data);
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
`Failed to load peaks from server (${e}), falling back to client-side render...`
|
||||
);
|
||||
wavesurfer.load(`${API_URL}/raw/${address}`);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
Loading…
Reference in a new issue