mirror of
https://github.com/nushell/nushell.git
synced 2025-04-13 15:58:19 +02:00
Plugin: from_mp4 and UntaggedValue::duration fix (#3618)
* plugin: basic from_mp4 implementation This patch introduces a very basic implementation of from_mp4, with only a few bits of meta-data available. The rest of the available meta-data (which is more than half left), will be included in a later patch * Mp4: Almost all track metadata is implemented Only meta-data that is not implemented is duration, facing some weird issue I am going to check on later * Mp4: All meta-data fields implemented All meta-data fields that can be retrieved are now retrieved, with the exception of duration for both tracks and the entire file itself because there is still an issue. However, that will be fixed in the upcoming patches * fix: UntaggedValue::duration() serializes correctly now Previous to this patch, there was an issue where when you would use UntaggedValue::duration() it would result in an invalid JSONRPC resulting string when using the protocol. This patch fixes this issue * Mp4: Duration fixed for file and tracks * plugins: Add plugin extra to src/plugins * Mp4: Replace unwrap() with expect() * Fix: Remove test mp4 file
This commit is contained in:
parent
a59414203f
commit
955a5ed8fb
28
Cargo.lock
generated
28
Cargo.lock
generated
@ -3087,6 +3087,20 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mp4"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "369762a9ab26451c57e0860102029db0f5c6b142baf6f373e759f311a828b50e"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes 0.5.6",
|
||||
"num-rational 0.3.2",
|
||||
"serde 1.0.126",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multiversion"
|
||||
version = "0.6.1"
|
||||
@ -3807,6 +3821,18 @@ dependencies = [
|
||||
"num-traits 0.2.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_from_mp4"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"mp4",
|
||||
"nu-errors",
|
||||
"nu-plugin",
|
||||
"nu-protocol",
|
||||
"nu-source",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_from_sqlite"
|
||||
version = "0.32.1"
|
||||
@ -4143,8 +4169,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-bigint 0.3.2",
|
||||
"num-integer",
|
||||
"num-traits 0.2.14",
|
||||
"serde 1.0.126",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -45,6 +45,7 @@ pub enum Primitive {
|
||||
/// A date value
|
||||
Date(DateTime<FixedOffset>),
|
||||
/// A count in the number of nanoseconds
|
||||
#[serde(with = "serde_bigint")]
|
||||
Duration(BigInt),
|
||||
/// A range of values
|
||||
Range(Box<Range>),
|
||||
|
20
crates/nu_plugin_from_mp4/Cargo.toml
Normal file
20
crates/nu_plugin_from_mp4/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "A converter plugin to the mp4 format for Nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_from_mp4"
|
||||
version = "0.1.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-errors = { path = "../nu-errors", version = "0.32.1" }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.32.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.32.1" }
|
||||
nu-source = { path = "../nu-source", version = "0.32.1" }
|
||||
tempfile = "3.2.0"
|
||||
mp4 = "0.8.2"
|
||||
|
||||
[build-dependencies]
|
174
crates/nu_plugin_from_mp4/src/from_mp4.rs
Normal file
174
crates/nu_plugin_from_mp4/src/from_mp4.rs
Normal file
@ -0,0 +1,174 @@
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, ReturnValue, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FromMp4 {
|
||||
pub state: Vec<u8>,
|
||||
pub name_tag: Tag,
|
||||
}
|
||||
|
||||
impl FromMp4 {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: vec![],
|
||||
name_tag: Tag::unknown(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_mp4_file_to_nu_value(path: &Path, tag: Tag) -> Result<Value, mp4::Error> {
|
||||
let mp4 = mp4::read_mp4(File::open(path).expect("Could not open mp4 file to read metadata"))?;
|
||||
|
||||
let mut dict = TaggedDictBuilder::new(tag.clone());
|
||||
|
||||
// Build tracks table
|
||||
let mut tracks = Vec::new();
|
||||
for track in mp4.tracks() {
|
||||
let mut curr_track_dict = TaggedDictBuilder::new(tag.clone());
|
||||
|
||||
curr_track_dict.insert_untagged("track id", UntaggedValue::int(track.track_id()));
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"track type",
|
||||
match track.track_type() {
|
||||
Ok(t) => UntaggedValue::string(t.to_string()),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"media type",
|
||||
match track.media_type() {
|
||||
Ok(t) => UntaggedValue::string(t.to_string()),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"box type",
|
||||
match track.box_type() {
|
||||
Ok(t) => UntaggedValue::string(t.to_string()),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged("width", UntaggedValue::int(track.width()));
|
||||
curr_track_dict.insert_untagged("height", UntaggedValue::int(track.height()));
|
||||
curr_track_dict.insert_untagged("frame_rate", UntaggedValue::from(track.frame_rate()));
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"sample freq index",
|
||||
match track.sample_freq_index() {
|
||||
Ok(sfi) => UntaggedValue::string(format!("{}", sfi.freq())), // this is a string for formatting reasons
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"channel config",
|
||||
match track.channel_config() {
|
||||
Ok(cc) => UntaggedValue::string(cc.to_string()),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged("language", UntaggedValue::string(track.language()));
|
||||
curr_track_dict.insert_untagged("timescale", UntaggedValue::int(track.timescale()));
|
||||
curr_track_dict.insert_untagged(
|
||||
"duration",
|
||||
UntaggedValue::duration(track.duration().as_nanos()),
|
||||
);
|
||||
curr_track_dict.insert_untagged("bitrate", UntaggedValue::int(track.bitrate()));
|
||||
curr_track_dict.insert_untagged("sample count", UntaggedValue::int(track.sample_count()));
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"video profile",
|
||||
match track.video_profile() {
|
||||
Ok(vp) => UntaggedValue::string(vp.to_string()),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"audio profile",
|
||||
match track.audio_profile() {
|
||||
Ok(ap) => UntaggedValue::string(ap.to_string()),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"sequence parameter set",
|
||||
match track.sequence_parameter_set() {
|
||||
Ok(sps) => UntaggedValue::string(format!("{:X?}", sps)),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"picture parameter set",
|
||||
match track.picture_parameter_set() {
|
||||
Ok(pps) => UntaggedValue::string(format!("{:X?}", pps)),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
// push curr track to tracks vec
|
||||
tracks.push(curr_track_dict.into_value());
|
||||
}
|
||||
|
||||
dict.insert_untagged("size", UntaggedValue::big_int(mp4.size()));
|
||||
|
||||
dict.insert_untagged(
|
||||
"major brand",
|
||||
UntaggedValue::string(mp4.major_brand().to_string()),
|
||||
);
|
||||
|
||||
dict.insert_untagged("minor version", UntaggedValue::int(mp4.minor_version()));
|
||||
|
||||
dict.insert_untagged(
|
||||
"compatible brands",
|
||||
UntaggedValue::string(format!("{:?}", mp4.compatible_brands())),
|
||||
);
|
||||
|
||||
dict.insert_untagged(
|
||||
"duration",
|
||||
UntaggedValue::duration(mp4.duration().as_nanos()),
|
||||
);
|
||||
|
||||
dict.insert_untagged("timescale", UntaggedValue::int(mp4.timescale()));
|
||||
dict.insert_untagged("is fragmented", UntaggedValue::boolean(mp4.is_fragmented()));
|
||||
dict.insert_untagged("tracks", UntaggedValue::Table(tracks).into_value(&tag));
|
||||
|
||||
Ok(dict.into_value())
|
||||
}
|
||||
|
||||
pub fn from_mp4_bytes_to_value(mut bytes: Vec<u8>, tag: Tag) -> Result<Value, std::io::Error> {
|
||||
let mut tempfile = tempfile::NamedTempFile::new()?;
|
||||
tempfile.write_all(bytes.as_mut_slice())?;
|
||||
match convert_mp4_file_to_nu_value(tempfile.path(), tag) {
|
||||
Ok(value) => Ok(value),
|
||||
Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_mp4(bytes: Vec<u8>, name_tag: Tag) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
match from_mp4_bytes_to_value(bytes, name_tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => Ok(list.into_iter().map(ReturnSuccess::value).collect()),
|
||||
_ => Ok(vec![ReturnSuccess::value(x)]),
|
||||
},
|
||||
Err(_) => Err(ShellError::labeled_error(
|
||||
"Could not parse as MP4",
|
||||
"input cannot be parsed as MP4",
|
||||
&name_tag,
|
||||
)),
|
||||
}
|
||||
}
|
4
crates/nu_plugin_from_mp4/src/lib.rs
Normal file
4
crates/nu_plugin_from_mp4/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod from_mp4;
|
||||
mod nu;
|
||||
|
||||
pub use from_mp4::FromMp4;
|
6
crates/nu_plugin_from_mp4/src/main.rs
Normal file
6
crates/nu_plugin_from_mp4/src/main.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_from_mp4::FromMp4;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut FromMp4::new())
|
||||
}
|
46
crates/nu_plugin_from_mp4/src/nu/mod.rs
Normal file
46
crates/nu_plugin_from_mp4/src/nu/mod.rs
Normal file
@ -0,0 +1,46 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::FromMp4;
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::Plugin;
|
||||
use nu_protocol::{CallInfo, Primitive, ReturnValue, Signature, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
impl Plugin for FromMp4 {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature::build("from mp4")
|
||||
.desc("Get meta-data of mp4 file")
|
||||
.filter())
|
||||
}
|
||||
|
||||
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
self.name_tag = call_info.name_tag;
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn filter(&mut self, input: Value) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
match input {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Binary(b)),
|
||||
..
|
||||
} => {
|
||||
self.state.extend_from_slice(&b);
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error_with_secondary(
|
||||
"Expected binary from pipeline",
|
||||
"requires binary input",
|
||||
self.name_tag.clone(),
|
||||
"value originates from here",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn end_filter(&mut self) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
crate::from_mp4::from_mp4(self.state.clone(), Tag::unknown())
|
||||
}
|
||||
}
|
1
crates/nu_plugin_from_mp4/src/nu/tests.rs
Normal file
1
crates/nu_plugin_from_mp4/src/nu/tests.rs
Normal file
@ -0,0 +1 @@
|
||||
mod integration {}
|
6
src/plugins/nu_plugin_extra_from_mp4.rs
Normal file
6
src/plugins/nu_plugin_extra_from_mp4.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_from_mp4::FromMp4;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut FromMp4::new())
|
||||
}
|
Loading…
Reference in New Issue
Block a user