Media

Audio info

An audio file's length, bitrate, sample rate, tags and cover art, for MP3, FLAC, M4A, Ogg, WAV and AIFF.

Version 1.0.0 · 3 functions · 1 crate

#audio#files#uploads#podcasts

Functions

What use audio_info.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use audio_info, audio_duration from audio_info (Quillo 0.6).

audio_info(path: str) → anyor an error

Everything known about an audio file: { format, duration, bitrate, sample_rate, channels, bit_depth, title, artist, album, album_artist, year, track, genre, has_cover }. duration is in seconds (a float), bitrate in kbit/s, sample_rate in Hz; what the file does not say is null.

audio_duration(path: str) → floator an error

The length of an audio file in seconds (a float): for a player, or to refuse uploads that are too long.

audio_cover(path: str, to: str) → stror an error

Saves the file's cover art (the front cover when there are several pictures) as to + ".jpg" or ".png" by its type, folders made: the path written. An error when it has none.

Get it

$ quillo install audio_info               # audio_info.qwer into this folder$ quillo install audio_info --example     # and its example, audio_info.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/audio_info.qwer$ curl -O https://quillolang.org/plugins/files/audio_info.qwe$ curl -O https://quillolang.org/plugins/files/audio_info_sample.mp3

Then, in your program, all of it or only what you call:

main.qwe
use audio_info.qweruse audio_info, audio_duration from audio_info      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
lofty = { version = "0.22", default-features = false }

Example

audio_info.qwe
// A podcast page: each episode's length and tags, and its cover art served beside it.////   quillo run audio_info.qwe      then open http://localhost:8080/episodeuse "audio_info.qwer"let EPISODE = "audio_info_sample.mp3"fn mmss(seconds) = `${floor(seconds / 60)}:${slice(str(100 + round(seconds) % 60), 1, 3)}`route GET /episode {    let info = audio_info(EPISODE)?    merge(info, { length: mmss(info.duration) })}route GET /cover {    let path = audio_cover(EPISODE, "data/covers/episode-1")?    file(path)}route GET / {    let info = audio_info(EPISODE)?    <html><body>        <img src="/cover" width="120" alt=""/>        <h1>{info.title}</h1>        <p>{info.artist} · {info.album} · {mmss(info.duration)}</p>        <audio controls src="/episode.mp3"></audio>    </body></html>}route GET /episode.mp3 = file(EPISODE)
$ quillo run audio_info.qwe$ curl 'localhost:8080/episode'

Source

audio_info.qwer
//! What an audio file holds: length, bitrate, sample rate, channels, its tags (title, artist, album...) and cover art, for MP3, FLAC, M4A, Ogg, Opus, WAV and AIFF.//! version: 1.0.0//!//! [dependencies]//! lofty = { version = "0.22", default-features = false }use lofty::file::TaggedFile;use lofty::prelude::*;use serde_json::{json, Value};const MAX_COVER: usize = 32 * 1024 * 1024;fn read(path: &str) -> Result<TaggedFile, String> {    let p = path.to_string();    // A damaged file must not take the server down with it.    match std::panic::catch_unwind(move || lofty::read_from_path(&p)) {        Ok(Ok(f)) => Ok(f),        Ok(Err(e)) => Err(format!("{}: {}", path, e)),        Err(_) => Err(format!("{}: the file could not be read", path)),    }}fn format_name(f: &TaggedFile) -> String {    format!("{:?}", f.file_type()).to_lowercase()}/// Everything known about an audio file: `{ format, duration, bitrate, sample_rate, channels, bit_depth, title, artist, album, album_artist, year, track, genre, has_cover }`./// `duration` is in seconds (a float), `bitrate` in kbit/s, `sample_rate` in Hz; what the file does not say is null.pub fn audio_info(path: &str) -> Result<Value, String> {    let f = read(path)?;    let p = f.properties();    let tag = f.primary_tag().or_else(|| f.first_tag());    let text = |v: Option<std::borrow::Cow<'_, str>>| v.map(|s| s.trim().to_string()).filter(|s| !s.is_empty());    Ok(json!({        "format": format_name(&f),        "duration": (p.duration().as_millis() as f64) / 1000.0,        "bitrate": p.audio_bitrate().or(p.overall_bitrate()),        "sample_rate": p.sample_rate(),        "channels": p.channels(),        "bit_depth": p.bit_depth(),        "title": tag.and_then(|t| text(t.title())),        "artist": tag.and_then(|t| text(t.artist())),        "album": tag.and_then(|t| text(t.album())),        "album_artist": tag.and_then(|t| t.get_string(&ItemKey::AlbumArtist).map(str::to_string)),        "year": tag.and_then(|t| t.year()),        "track": tag.and_then(|t| t.track()),        "genre": tag.and_then(|t| text(t.genre())),        "has_cover": f.tags().iter().any(|t| !t.pictures().is_empty()),    }))}/// The length of an audio file in seconds (a float): for a player, or to refuse uploads that are too long.pub fn audio_duration(path: &str) -> Result<f64, String> {    Ok(read(path)?.properties().duration().as_millis() as f64 / 1000.0)}/// Saves the file's cover art (the front cover when there are several pictures) as `to` + ".jpg" or ".png" by its type, folders made: the path written. An error when it has none.pub fn audio_cover(path: &str, to: &str) -> Result<String, String> {    let f = read(path)?;    let pics: Vec<_> = f.tags().iter().flat_map(|t| t.pictures().iter()).collect();    let pic = pics.iter().find(|p| p.pic_type() == lofty::picture::PictureType::CoverFront).or(pics.first()).ok_or_else(|| format!("{}: no cover art", path))?;    if pic.data().len() > MAX_COVER {        return Err(format!("{}: the cover is larger than 32 MB", path));    }    let ext = match pic.mime_type() {        Some(lofty::picture::MimeType::Png) => "png",        Some(lofty::picture::MimeType::Gif) => "gif",        Some(lofty::picture::MimeType::Bmp) => "bmp",        _ => if pic.data().starts_with(&[0x89, b'P']) { "png" } else { "jpg" },    };    let out = format!("{}.{}", to, ext);    if let Some(dir) = std::path::Path::new(&out).parent().filter(|d| !d.as_os_str().is_empty()) {        std::fs::create_dir_all(dir).map_err(|e| format!("{}: {}", dir.display(), e))?;    }    std::fs::write(&out, pic.data()).map_err(|e| format!("{}: {}", out, e))?;    Ok(out)}
Audio tags audio_tags.qwer · 1.0.0Audio tags written as well as read, for MP3, FLAC, Ogg, M4A, WAV and AIFF: title, artist, album, track, year, genre, composer, lyrics, BPM and more, cover art set, and every tag stripped; written to a copy.audiofilespodcastsprivacy8 views · 7 downloadsLoudness loudness.qwer · 1.0.0Loudness of WAV audio as broadcasters measure it (EBU R128): integrated LUFS, loudness range and true peak; silences found; leading and trailing silence trimmed; and files normalised to -16, -14 or -23 LUFS under a peak limit.audiopodcastssoundsfiles7 views · 9 downloadsMIDI files midi.qwer · 1.0.0MIDI files read and written: each track's notes with their names, start and length in beats and seconds, tempo and time signature, instruments by General MIDI name; songs written from lists of notes; notes named and transposed.audiofilessoundsgames7 views · 7 downloadsVideo info video_info.qwer · 1.0.0An MP4 or MOV video's length, size in pixels, frame rate, codecs, bitrate and tracks, read from its header.videofilesuploads7 views · 7 downloadsWAV wav.qwer · 1.0.0WAV files made and read: tones, melodies and silence for alerts and tests, joined end to end, and a file's length, peak and loudness.audiosoundsfilesalerts7 views · 7 downloads3D models models_3d.qwer · 1.0.0STL and OBJ models read: triangles, size, volume, surface area and whether the mesh is closed, a 3D-print quote by weight, and OBJ turned into binary STL.filesuploadsprintingcalculators6 views · 7 downloads
Thread
68Useful? Like it, or add to its thread below.

Thread 0 posts

On Audio info: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.