Media

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

#video#files#uploads

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 error

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".

video_tracks(path: str) → listor an error

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.

video_duration(path: str) → floator an error

The 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.qwe

Or 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.mp4

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

main.qwe
use video_info.qweruse video_info, video_tracks from video_info      // Quillo 0.6: only these

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

[dependencies]
mp4 = "0.14"

Example

video_info.qwe
// 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

video_info.qwer
//! 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()))}
Thread
77Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.