Skip to content

Commit d116240

Browse files
committed
Improved recording experience
- Added setup command to build a videos directory at ~/Videos/spur - Added filename support - Still need to add randomly generated filenames based on timestamps
1 parent 33a501d commit d116240

File tree

9 files changed

+195
-94
lines changed

9 files changed

+195
-94
lines changed

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@ scrap = "0.5.0"
1010
v4l = {version = "0.12.1", features = ["v4l2-sys"] }
1111
gstreamer = "0.17.4"
1212
gstreamer-video = "0.17.2"
13-
num-rational = "0.4.0"
13+
num-rational = "0.4.0"
14+
15+
home = "0.5.3"
16+
chrono = "0.4"

src/constants.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
pub const AUTHOR: &str = "nsrCodes";
22
pub const DESCRIPTION: &str = "Make easily sharable recordings with ease";
33
pub const CMD: &str = "spur";
4-
pub const VERSION: &str = "0.1.0";
4+
pub const VERSION: &str = "0.1.1";
5+
6+
/* App constants */
7+
pub const CONFIG_FILE_NAME: &str = "spur_config.txt";
8+
pub const VIDEOS_FOLDER_FROM_HOME: &str = "Videos/spur";
9+
pub const STREAM_API: &str = "rtmp://someendpoint:1935"; // todo : get from env
10+
pub const STREAM_API_PATH: &str = "live"; // todo : get from env

src/lib.rs

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ pub mod constants;
44
pub mod options;
55
pub mod overlay;
66
pub mod parser;
7+
pub mod paths;
78
pub mod recorder;
89
pub mod session;
910
pub mod streamer;
1011

1112
#[derive(Debug)]
1213
pub enum CustomError {
1314
InvalidAnswer,
15+
CouldNotFindHome,
1416
}
1517

1618
#[derive(Debug)]
@@ -25,45 +27,37 @@ pub enum ThreadMessages {
2527
#[derive(Debug, Clone)]
2628
pub struct Config {
2729
pub s_type: SType,
28-
pub name: String,
29-
pub cmd: &'static str,
30-
pub path: &'static str,
30+
pub filename: String,
31+
pub path: String,
3132
pub quality: Quality,
3233
pub framerate: FrameRate,
3334
pub overlay: bool,
3435
}
3536

3637
impl Config {
37-
pub fn new(st: SType) -> Self {
38-
let (name, path) = match st {
39-
// Make tis a function
40-
SType::Record => (SType::to_string(&SType::Record), "rtmp://someendpoint:1935"),
41-
SType::Stream => (SType::to_string(&SType::Stream), "my-demo.mkv"),
42-
};
38+
pub fn new(st: SType, filename: String) -> Self {
39+
let path = st.get_target_path(&filename);
40+
4341
Config {
44-
cmd: constants::CMD,
45-
name,
42+
filename: String::from(filename),
4643
path,
4744
framerate: FrameRate::default(),
4845
quality: Quality::default(),
4946
overlay: overlay::default(),
5047
s_type: st,
5148
}
5249
}
50+
51+
// CURRENTLY, NOT USED
5352
pub fn update_session_type(&mut self, st: SType) {
5453
self.s_type = st;
55-
let (name, path) = match st {
56-
SType::Record => (SType::to_string(&SType::Record), "rtmp://someendpoint:1935"),
57-
SType::Stream => (SType::to_string(&SType::Stream), "my-demo.mkv"),
58-
};
59-
self.name = name;
60-
self.path = path;
54+
self.path = st.get_target_path(&self.filename);
6155
}
62-
pub fn create_path_arg<'a>(&self) -> Arg<'a> {
56+
pub fn create_path_arg(&self) -> Arg {
6357
Arg::new("path")
6458
.long("path")
6559
.takes_value(self.s_type == SType::Record)
66-
.default_value(self.path)
60+
.default_value(self.path.as_str())
6761
.help("Set path of recording")
6862
}
6963

@@ -82,6 +76,14 @@ impl Config {
8276
.help("Show current config settings")
8377
}
8478
}
79+
80+
impl Default for Config {
81+
fn default() -> Self {
82+
let default_st = SType::default();
83+
Config::new(default_st, parser::get_filename_from_arg(default_st, None))
84+
}
85+
}
86+
8587
/*Media*/
8688
pub trait Media {
8789
fn new(config: Config) -> Self
@@ -92,9 +94,3 @@ pub trait Media {
9294
fn cancel_stream(&self);
9395
fn create_pipeline(&mut self);
9496
}
95-
96-
impl Default for Config {
97-
fn default() -> Self {
98-
Config::new(SType::default())
99-
}
100-
}

src/options.rs

Lines changed: 69 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,80 @@
1-
use crate::CustomError;
1+
use crate::{paths, CustomError};
22
use clap::Arg;
33
use std::str::FromStr;
4+
45
pub trait MetaOption {
56
const COMMAND_NAME: &'static str;
67
fn values() -> [&'static str; 2];
78
fn create_arg<'a>() -> Arg<'a>;
89
}
910

11+
/** Type */
12+
#[derive(PartialEq, Debug, Clone, Copy)]
13+
pub enum SType {
14+
Record = 0,
15+
Stream = 1,
16+
}
17+
18+
impl SType {
19+
pub fn get_name(&self) -> String {
20+
SType::to_string(self)
21+
}
22+
23+
pub fn get_target_path(&self, filename: &String) -> String {
24+
match self {
25+
// todo: convert this into a function
26+
SType::Record => paths::get_video_path(filename)
27+
.as_path()
28+
.display()
29+
.to_string(),
30+
SType::Stream => paths::get_stream_path(filename),
31+
}
32+
}
33+
}
34+
35+
impl Default for SType {
36+
fn default() -> Self {
37+
SType::Stream
38+
}
39+
}
40+
41+
impl FromStr for SType {
42+
type Err = CustomError;
43+
44+
fn from_str(s: &str) -> Result<Self, Self::Err> {
45+
match s {
46+
"record" => Ok(SType::Record),
47+
"stream" => Ok(SType::Stream),
48+
_ => Err(CustomError::InvalidAnswer),
49+
}
50+
}
51+
}
52+
53+
impl ToString for SType {
54+
fn to_string(&self) -> String {
55+
match self {
56+
&Self::Record => String::from("record"),
57+
&Self::Stream => String::from("stream"),
58+
}
59+
}
60+
}
61+
impl MetaOption for SType {
62+
const COMMAND_NAME: &'static str = "session";
63+
fn values() -> [&'static str; 2] {
64+
const SUB_COMMANDS: [&'static str; 2] = ["record", "stream"];
65+
SUB_COMMANDS
66+
}
67+
68+
fn create_arg<'a>() -> Arg<'a> {
69+
Arg::new(Self::COMMAND_NAME)
70+
.long(Self::COMMAND_NAME)
71+
.takes_value(true)
72+
.default_value("record")
73+
.required(false)
74+
.help("Wether to stream to server or store to storage")
75+
}
76+
}
77+
1078
/** Quality */
1179
#[derive(Debug, Clone, Copy)]
1280
pub enum Quality {
@@ -104,53 +172,3 @@ impl MetaOption for FrameRate {
104172
.help("Framerate of recording")
105173
}
106174
}
107-
108-
/** Type */
109-
#[derive(PartialEq, Debug, Clone, Copy)]
110-
pub enum SType {
111-
Record = 0,
112-
Stream = 1,
113-
}
114-
115-
impl Default for SType {
116-
fn default() -> Self {
117-
SType::Stream
118-
}
119-
}
120-
121-
impl FromStr for SType {
122-
type Err = CustomError;
123-
124-
fn from_str(s: &str) -> Result<Self, Self::Err> {
125-
match s {
126-
"record" => Ok(SType::Record),
127-
"stream" => Ok(SType::Stream),
128-
_ => Err(CustomError::InvalidAnswer),
129-
}
130-
}
131-
}
132-
133-
impl ToString for SType {
134-
fn to_string(&self) -> String {
135-
match self {
136-
&Self::Record => String::from("record"),
137-
&Self::Stream => String::from("stream"),
138-
}
139-
}
140-
}
141-
impl MetaOption for SType {
142-
const COMMAND_NAME: &'static str = "session";
143-
fn values() -> [&'static str; 2] {
144-
const SUB_COMMANDS: [&'static str; 2] = ["record", "stream"];
145-
SUB_COMMANDS
146-
}
147-
148-
fn create_arg<'a>() -> Arg<'a> {
149-
Arg::new(Self::COMMAND_NAME)
150-
.long(Self::COMMAND_NAME)
151-
.takes_value(true)
152-
.default_value("record")
153-
.required(false)
154-
.help("Wether to stream to server or store to storage")
155-
}
156-
}

src/overlay.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ impl Media for CameraPreview {
7676

7777
fn cancel_stream(&self) {}
7878
fn create_pipeline(&mut self) {
79-
println!("Handling stream");
8079
let rate = Ratio::new(self.config.framerate as i32, 1);
8180
const WIDTH: i32 = 400;
8281
// const WIDTH: i32 = 800;
@@ -177,8 +176,8 @@ impl Media for CameraPreview {
177176
caps.set_data("caps", capsfilter);
178177

179178
match caps.data::<Caps>("caps") {
180-
Some(data) => {
181-
println!("{:?}", data.as_ref());
179+
Some(_data) => {
180+
// println!("{:?}", data.as_ref());
182181
}
183182
None => println!("No data"),
184183
}

src/parser.rs

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,42 @@
11
use crate::{
22
constants,
33
options::{FrameRate, MetaOption, Quality, SType},
4-
overlay, parser,
4+
overlay, parser, paths,
55
session::Session,
66
Config,
77
};
8-
use clap::{ArgMatches, Command};
9-
use std::str::FromStr;
8+
use clap::{Arg, ArgMatches, Command};
9+
use std::{fs, io::Write, process, str::FromStr};
1010

11-
fn create_sub_command<'a>(st: SType) -> Command<'a> {
12-
let conf = Config::new(st);
13-
let path = conf.create_path_arg();
11+
/* Utils */
12+
// use chrono;
1413

15-
Command::new(conf.name).args([
14+
fn create_sub_command(st: SType) -> Command<'static> {
15+
Command::new(st.get_name()).args([
1616
overlay::create_arg(),
1717
Quality::create_arg(),
1818
FrameRate::create_arg(),
19-
path,
19+
Arg::new("name")
20+
.long("filename")
21+
.short('n')
22+
.takes_value(true)
23+
.help("Name of recorded video"),
2024
])
2125
}
2226

27+
pub fn get_filename_from_arg(st: SType, arg_filename: Option<&str>) -> String {
28+
let name = match arg_filename {
29+
Some(fname) => fname,
30+
// None => format!("{:?}", chrono::offset::Utc::now()).as_str(), // todo : generate from datetime
31+
None => "123", // todo : generate from datetime
32+
};
33+
match st {
34+
SType::Record => format!("{}.mkv", name),
35+
SType::Stream => format!("/{}/{}", constants::STREAM_API_PATH, name),
36+
}
37+
}
38+
39+
/* Parser */
2340
pub fn parse_args() -> ArgMatches {
2441
return Command::new("spur")
2542
.version(constants::VERSION)
@@ -29,16 +46,50 @@ pub fn parse_args() -> ArgMatches {
2946
.subcommands([
3047
create_sub_command(SType::Record),
3148
create_sub_command(SType::Stream),
49+
Command::new("setup").about("setting up spur on your machine"),
3250
])
3351
.get_matches();
3452
}
3553

3654
pub fn create_session_from_args() -> Session {
37-
let mut conf = Config::default();
3855
let matches = parser::parse_args();
3956
match matches.subcommand() {
57+
Some(("setup", _)) => {
58+
// todo : abstract to function
59+
// Creating a Videos directory
60+
let videos_path = paths::get_video_directory_path();
61+
62+
fs::DirBuilder::new()
63+
.recursive(true)
64+
.create(&videos_path)
65+
.unwrap_or_else(|err| {
66+
println!("Couldn't create directory - {}", videos_path.display());
67+
println!("Error - {:?}", err);
68+
process::exit(1);
69+
});
70+
71+
// CURRENTLY, NOT USED
72+
// Saving conf to text file
73+
let home_path = paths::get_home_path().expect("Couldn't find your Home directory");
74+
let mut conf_file = fs::File::create(format!(
75+
"{}/{}",
76+
home_path.display(),
77+
constants::CONFIG_FILE_NAME
78+
))
79+
.expect("Could not create conf file");
80+
conf_file
81+
.write_all(format!("{}", videos_path.display()).as_bytes()) // hacky
82+
.expect("Could not write to conf file");
83+
println!("-------------- Setup is complete ------------------------");
84+
process::exit(0);
85+
}
4086
Some((cmd_str, sub_match)) => {
41-
conf.s_type = SType::from_str(cmd_str).unwrap();
87+
// Creating config for new session
88+
let st = SType::from_str(cmd_str).unwrap();
89+
let arg_filename = sub_match.value_of("name");
90+
let mut conf = Config::new(st, get_filename_from_arg(st, arg_filename));
91+
92+
// Updating config with parsed parameters
4293
let arg_quality = sub_match
4394
.value_of(Quality::COMMAND_NAME)
4495
.unwrap_or_default();

0 commit comments

Comments
 (0)