Video info
An MP4 or MOV video's length, size in pixels, frame rate, codecs, bitrate and tracks, read from its header.
Version 1.0.0 · 3 functions · 1 crate
Functions
What use video_info.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use video_info, video_tracks from video_info (Quillo 0.6).
video_info(path: str) → anyor an errorA summary of the video: { duration, width, height, fps, video_codec, audio_codec, sample_rate, channels, bitrate, size, brand }. duration in seconds (a float), bitrate in kbit/s for the whole file, size in bytes; null for what it lacks (no audio track, say). Codecs are h264, h265, vp9, aac, ttxt or "unknown".
video_tracks(path: str) → listor an errorEach track as { id, kind, codec, duration, bitrate, language }, with width, height and fps for video and sample_rate and channels for audio. kind is video, audio, subtitle or other.
video_duration(path: str) → floator an errorThe length of the video in seconds (a float): to refuse uploads that are too long, or to show it.
Get it
$ quillo install video_info # video_info.qwer into this folder$ quillo install video_info --example # and its example, video_info.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/video_info.qwer$ curl -O https://quillolang.org/plugins/files/video_info.qwe$ curl -O https://quillolang.org/plugins/files/video_info_sample.mp4Then, in your program, all of it or only what you call:
use video_info.qweruse video_info, video_tracks from video_info // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
mp4 = "0.14"Example
// An upload check for videos: refuse those too long or too large, and show what was sent.//// quillo run video_info.qwe then open http://localhost:8080/clipuse "video_info.qwer"let CLIP = "video_info_sample.mp4"route GET /clip = video_info(CLIP)route GET /tracks = video_tracks(CLIP)route POST /upload { let f = request.files.video ?? reply 400 "Choose a video" let path = save_upload(f, `data/videos/${uuid()}.mp4`)? let info = video_info(path) if is_err(info) { delete_file(path); reply 415 "MP4 or MOV videos only" } if info.duration > 60 || info.height > 1080 { delete_file(path); reply 413 "Up to a minute, up to 1080p" } { saved: path, seconds: info.duration, size: `${info.width}×${info.height}` }}$ quillo run video_info.qwe$ curl 'localhost:8080/clip'$ curl 'localhost:8080/tracks'Source
//! What an MP4 or MOV video holds: length, size in pixels, frame rate, codecs, bitrate and tracks, read from its header without decoding it.//! version: 1.0.0//!//! [dependencies]//! mp4 = "0.14"use serde_json::{json, Value};const MAX_TRACKS: usize = 64;fn read(path: &str) -> Result<(mp4::Mp4Reader<std::io::BufReader<std::fs::File>>, u64), String> { let f = std::fs::File::open(path).map_err(|e| format!("{}: {}", path, e))?; let size = f.metadata().map_err(|e| e.to_string())?.len(); let p = path.to_string(); let r = std::panic::catch_unwind(move || mp4::Mp4Reader::read_header(std::io::BufReader::new(f), size)); match r { Ok(Ok(m)) => Ok((m, size)), Ok(Err(e)) => Err(format!("{}: not an MP4 or MOV file it can read ({})", p, e)), Err(_) => Err(format!("{}: the file could not be read", p)), }}fn secs(d: std::time::Duration) -> f64 { (d.as_millis() as f64) / 1000.0}fn track(t: &mp4::Mp4Track) -> Value { let kind = t.track_type().map(|k| k.to_string().to_lowercase()).unwrap_or_else(|_| "other".into()); let codec = t.media_type().map(|m| m.to_string()).unwrap_or_else(|_| "unknown".into()); let mut v = json!({ "id": t.track_id(), "kind": kind, "codec": codec, "duration": secs(t.duration()), "bitrate": t.bitrate() / 1000, "language": t.language(), }); if kind == "video" { v["width"] = t.width().into(); v["height"] = t.height().into(); v["fps"] = ((t.frame_rate() * 100.0).round() / 100.0).into(); } if kind == "audio" { v["sample_rate"] = t.sample_freq_index().map(|f| Value::from(f.freq())).unwrap_or(Value::Null); v["channels"] = t.channel_config().map(|c| Value::from(c as u8)).unwrap_or(Value::Null); } v}fn tracks_of(m: &mp4::Mp4Reader<std::io::BufReader<std::fs::File>>) -> Vec<Value> { let mut ids: Vec<&u32> = m.tracks().keys().collect(); ids.sort(); ids.into_iter().take(MAX_TRACKS).map(|id| track(&m.tracks()[id])).collect()}/// A summary of the video: `{ duration, width, height, fps, video_codec, audio_codec, sample_rate, channels, bitrate, size, brand }`./// `duration` in seconds (a float), `bitrate` in kbit/s for the whole file, `size` in bytes; null for what it lacks (no audio track, say)./// Codecs are h264, h265, vp9, aac, ttxt or "unknown".pub fn video_info(path: &str) -> Result<Value, String> { let (m, size) = read(path)?; let tracks = tracks_of(&m); let video = tracks.iter().find(|t| t["kind"] == "video"); let audio = tracks.iter().find(|t| t["kind"] == "audio"); let duration = secs(m.duration()); let get = |t: Option<&Value>, k: &str| t.map(|t| t[k].clone()).unwrap_or(Value::Null); Ok(json!({ "duration": duration, "width": get(video, "width"), "height": get(video, "height"), "fps": get(video, "fps"), "video_codec": get(video, "codec"), "audio_codec": get(audio, "codec"), "sample_rate": get(audio, "sample_rate"), "channels": get(audio, "channels"), "bitrate": if duration > 0.0 { ((size as f64 * 8.0 / duration) / 1000.0).round() as i64 } else { 0 }, "size": size, "brand": m.major_brand().to_string().trim().to_string(), }))}/// Each track as `{ id, kind, codec, duration, bitrate, language }`, with `width`, `height` and `fps` for video and `sample_rate` and `channels` for audio./// `kind` is video, audio, subtitle or other.pub fn video_tracks(path: &str) -> Result<Vec<Value>, String> { Ok(tracks_of(&read(path)?.0))}/// The length of the video in seconds (a float): to refuse uploads that are too long, or to show it.pub fn video_duration(path: &str) -> Result<f64, String> { Ok(secs(read(path)?.0.duration()))}Related
audio_info.qwer · 1.0.0An audio file's length, bitrate, sample rate, tags and cover art, for MP3, FLAC, M4A, Ogg, WAV and AIFF.6 views · 8 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.6 views · 7 downloadsDisk space disk_space.qwer · 1.0.0Free and total space of the disk a path is on, for health checks, refusing uploads before the disk fills, and admin pages.8 views · 8 downloadsFile type file_type.qwer · 1.0.0What a file really is from its first bytes, not its name: MIME type, extension and kind, for upload checks.7 views · 9 downloadsFonts fonts.qwer · 1.0.0A font file's names, weight and style, glyph count, which characters it covers, and how wide a text is set in it.5 views · 7 downloadsMIME types mime.qwer · 1.0.0MIME types from file names and extensions, the extensions of a type, and Content-Type headers with a charset for text.8 views · 7 downloadsThread 0 posts
On Video info: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.