Skip to content

Commit f4f88f1

Browse files
committed
08.03.2024
* FFmpeg feature implemented * `bytes` create implemented for download functions * Unhandled download panic fixed (thanks @Timtam)
1 parent 3f4c042 commit f4f88f1

File tree

18 files changed

+404
-180
lines changed

18 files changed

+404
-180
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rusty_ytdl"
3-
version = "0.6.7"
3+
version = "0.7.0"
44
authors = ["Mithronn"]
55
edition = "2021"
66
description = "A Rust library for Youtube video searcher and downloader"
@@ -55,7 +55,7 @@ bytes = "1.5.0"
5555
tokio = { version = "1.36.0", features = ["full"] }
5656

5757
[features]
58-
default = ["search", "live", "default-tls", "ffmpeg"]
58+
default = ["search", "live", "default-tls"]
5959
live = ["tokio/time", "tokio/process"]
6060
blocking = ["tokio/rt", "tokio/rt-multi-thread"]
6161
search = []

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Download videos **blazing-fast** without getting stuck on Youtube download speed
1919

2020
## Roadmap
2121

22-
- [ ] ffmpeg feature
22+
- [x] ffmpeg feature
2323
- [ ] benchmarks
2424

2525
## Features
@@ -28,6 +28,7 @@ Download videos **blazing-fast** without getting stuck on Youtube download speed
2828
- Search with query (Video, Playlist, Channel)
2929
- Blocking and asynchronous API
3030
- Proxy, IPv6, and cookie support on request
31+
- Built-in FFmpeg audio and video filter apply support. [Example](examples/download_with_ffmpeg.rs)
3132
- [CLI](https://crates.io/crates/rusty_ytdl-cli)
3233

3334
# Usage
@@ -158,5 +159,5 @@ Or add the following to your `Cargo.toml` file:
158159

159160
```toml
160161
[dependencies]
161-
rusty_ytdl = "0.6.7"
162+
rusty_ytdl = "0.7.0"
162163
```

cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ tokio = { version = "1.28.2", features = [
3030
"io-std",
3131
] }
3232
log = "0.4.20"
33-
rusty_ytdl = { path = "..", version = "0.6.7" }
33+
rusty_ytdl = { path = "..", version = "0.7.0" }
3434
colored = { version = "2.0.0" }
3535
indicatif = "0.17.4"
3636
fern = { version = "0.6.2", features = ["colored"] }

examples/download_with_ffmpeg.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#[tokio::main]
2+
async fn main() {
3+
#[cfg(feature = "ffmpeg")]
4+
{
5+
use rusty_ytdl::{FFmpegArgs, Video, VideoOptions, VideoQuality, VideoSearchOptions};
6+
7+
let url = "FZ8BxMU3BYc";
8+
9+
let video_options = VideoOptions {
10+
quality: VideoQuality::Highest,
11+
filter: VideoSearchOptions::VideoAudio,
12+
..Default::default()
13+
};
14+
15+
let video = Video::new_with_options(url, video_options).unwrap();
16+
17+
let stream = video
18+
.stream_with_ffmpeg(Some(FFmpegArgs {
19+
format: Some("mp3".to_string()),
20+
audio_filter: Some("aresample=48000,asetrate=48000*0.8".to_string()),
21+
video_filter: Some("eq=brightness=150:saturation=2".to_string()),
22+
}))
23+
.await
24+
.unwrap();
25+
26+
while let Some(chunk) = stream.chunk().await.unwrap() {
27+
println!("{:#?}", chunk);
28+
}
29+
}
30+
}

src/blocking/info.rs

Lines changed: 112 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,7 @@ impl Video {
5656
/// println!("{:#?}", chunk);
5757
/// }
5858
/// ```
59-
pub fn stream(
60-
&self,
61-
#[cfg(feature = "ffmpeg")] ffmpeg_args: Option<FFmpegArgs>,
62-
) -> Result<Box<dyn Stream + Send + Sync>, VideoError> {
59+
pub fn stream(&self) -> Result<Box<dyn Stream + Send + Sync>, VideoError> {
6360
let client = self.0.get_client();
6461

6562
let options = self.0.get_options();
@@ -81,25 +78,21 @@ impl Video {
8178
let stream = LiveStream::new(LiveStreamOptions {
8279
client: Some(client.clone()),
8380
stream_url: link,
84-
});
85-
86-
if stream.is_err() {
87-
return Err(stream.err().unwrap());
88-
}
81+
})?;
8982

90-
return Ok(Box::new(stream.unwrap()));
83+
return Ok(Box::new(stream));
9184
}
9285
#[cfg(not(feature = "live"))]
9386
{
9487
return Err(VideoError::LiveStreamNotSupported);
9588
}
9689
}
9790

98-
let dl_chunk_size = if options.download_options.dl_chunk_size.is_some() {
99-
options.download_options.dl_chunk_size.unwrap()
100-
} else {
101-
1024 * 1024 * 10_u64 // -> Default is 10MB to avoid Youtube throttle (Bigger than this value can be throttle by Youtube)
102-
};
91+
let dl_chunk_size = options
92+
.download_options
93+
.dl_chunk_size
94+
// 1024 * 1024 * 10_u64 -> Default is 10MB to avoid Youtube throttle (Bigger than this value can be throttle by Youtube)
95+
.unwrap_or(1024 * 1024 * 10_u64);
10396

10497
let start = 0;
10598
let end = start + dl_chunk_size;
@@ -114,13 +107,102 @@ impl Video {
114107
if content_length == 0 {
115108
let content_length_response = block_async!(client.get(&link).send())
116109
.map_err(VideoError::ReqwestMiddleware)?
117-
.content_length();
110+
.content_length()
111+
.ok_or(VideoError::VideoNotFound)?;
112+
113+
content_length = content_length_response;
114+
}
115+
116+
let stream = NonLiveStream::new(NonLiveStreamOptions {
117+
client: Some(client.clone()),
118+
link,
119+
content_length,
120+
dl_chunk_size,
121+
start,
122+
end,
123+
#[cfg(feature = "ffmpeg")]
124+
ffmpeg_args: None,
125+
})?;
118126

119-
if content_length_response.is_none() {
120-
return Err(VideoError::VideoNotFound);
127+
Ok(Box::new(stream))
128+
}
129+
130+
#[cfg(feature = "ffmpeg")]
131+
/// Try to turn [`Stream`] implemented [`LiveStream`] or [`NonLiveStream`] depend on the video with [`FFmpegArgs`].
132+
/// If function successfully return can download video with applied ffmpeg filters and formats chunk by chunk
133+
/// # Example
134+
/// ```ignore
135+
/// let video_url = "https://www.youtube.com/watch?v=FZ8BxMU3BYc";
136+
///
137+
/// let video = Video::new(video_url).unwrap();
138+
///
139+
/// let stream = video.stream().await.unwrap();
140+
///
141+
/// while let Some(chunk) = stream.chunk().await.unwrap() {
142+
/// println!("{:#?}", chunk);
143+
/// }
144+
/// ```
145+
pub async fn stream_with_ffmpeg(
146+
&self,
147+
ffmpeg_args: Option<FFmpegArgs>,
148+
) -> Result<Box<dyn Stream + Send + Sync>, VideoError> {
149+
let client = self.0.get_client();
150+
151+
let options = self.0.get_options();
152+
153+
let info = block_async!(self.0.get_info())?;
154+
let format = choose_format(&info.formats, &options)
155+
.map_err(|_op| VideoError::VideoSourceNotFound)?;
156+
157+
let link = format.url;
158+
159+
if link.is_empty() {
160+
return Err(VideoError::VideoSourceNotFound);
161+
}
162+
163+
// Only check for HLS formats for live streams
164+
if format.is_hls {
165+
#[cfg(feature = "live")]
166+
{
167+
let stream = LiveStream::new(LiveStreamOptions {
168+
client: Some(client.clone()),
169+
stream_url: link,
170+
})?;
171+
172+
return Ok(Box::new(stream));
121173
}
174+
#[cfg(not(feature = "live"))]
175+
{
176+
return Err(VideoError::LiveStreamNotSupported);
177+
}
178+
}
179+
180+
let dl_chunk_size = options
181+
.download_options
182+
.dl_chunk_size
183+
// 1024 * 1024 * 10_u64 -> Default is 10MB to avoid Youtube throttle (Bigger than this value can be throttle by Youtube)
184+
.unwrap_or(1024 * 1024 * 10_u64);
122185

123-
content_length = content_length_response.unwrap();
186+
let start = 0;
187+
let end = start + dl_chunk_size;
188+
189+
let mut content_length = format
190+
.content_length
191+
.unwrap_or("0".to_string())
192+
.parse::<u64>()
193+
.unwrap_or(0);
194+
195+
// Get content length from source url if content_length is 0
196+
if content_length == 0 {
197+
let content_length_response = client
198+
.get(&link)
199+
.send()
200+
.await
201+
.map_err(VideoError::ReqwestMiddleware)?
202+
.content_length()
203+
.ok_or(VideoError::VideoNotFound)?;
204+
205+
content_length = content_length_response;
124206
}
125207

126208
let stream = NonLiveStream::new(NonLiveStreamOptions {
@@ -130,28 +212,27 @@ impl Video {
130212
dl_chunk_size,
131213
start,
132214
end,
133-
#[cfg(feature = "ffmpeg")]
134215
ffmpeg_args,
135216
})?;
136217

137218
Ok(Box::new(stream))
138219
}
139220

140221
/// Download video directly to the file
141-
pub fn download<P: AsRef<std::path::Path>>(
222+
pub fn download<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), VideoError> {
223+
Ok(block_async!(self.0.download(path))?)
224+
}
225+
226+
#[cfg(feature = "ffmpeg")]
227+
/// Download video with ffmpeg args directly to the file
228+
pub async fn download_with_ffmpeg<P: AsRef<std::path::Path>>(
142229
&self,
143230
path: P,
144-
#[cfg(feature = "ffmpeg")] ffmpeg_args: Option<FFmpegArgs>,
231+
ffmpeg_args: Option<FFmpegArgs>,
145232
) -> Result<(), VideoError> {
146-
#[cfg(feature = "ffmpeg")]
147-
{
148-
Ok(block_async!(self.0.download(path, ffmpeg_args))?)
149-
}
150-
151-
#[cfg(not(feature = "ffmpeg"))]
152-
{
153-
Ok(block_async!(self.0.download(path))?)
154-
}
233+
Ok(block_async!(self
234+
.0
235+
.download_with_ffmpeg(path, ffmpeg_args))?)
155236
}
156237

157238
/// Get video URL

src/blocking/stream/streams/live.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use bytes::Bytes;
2+
13
use crate::blocking::stream::Stream;
24
use crate::stream::{LiveStream as AsyncLiveStream, LiveStreamOptions};
35
use crate::{block_async, VideoError};
@@ -11,7 +13,7 @@ impl LiveStream {
1113
}
1214

1315
impl Stream for LiveStream {
14-
fn chunk(&self) -> Result<Option<Vec<u8>>, VideoError> {
16+
fn chunk(&self) -> Result<Option<Bytes>, VideoError> {
1517
use crate::stream::Stream;
1618
Ok(block_async!(self.0.chunk())?)
1719
}

src/blocking/stream/streams/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use bytes::Bytes;
2+
13
#[cfg(feature = "live")]
24
pub use crate::stream::LiveStreamOptions;
35
pub use crate::stream::NonLiveStreamOptions;
@@ -16,7 +18,7 @@ pub trait Stream {
1618
/// Stream a chunk of the [`u8`] bytes
1719
///
1820
/// When the bytes has been exhausted, this will return `None`.
19-
fn chunk(&self) -> Result<Option<Vec<u8>>, VideoError>;
21+
fn chunk(&self) -> Result<Option<Bytes>, VideoError>;
2022

2123
/// Content length of the stream
2224
///

src/blocking/stream/streams/non_live.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use bytes::Bytes;
2+
13
use crate::blocking::stream::Stream;
24
use crate::stream::{NonLiveStream as AsyncNonLiveStream, NonLiveStreamOptions};
35
use crate::{block_async, VideoError};
@@ -11,7 +13,7 @@ impl NonLiveStream {
1113
}
1214

1315
impl Stream for NonLiveStream {
14-
fn chunk(&self) -> Result<Option<Vec<u8>>, VideoError> {
16+
fn chunk(&self) -> Result<Option<Bytes>, VideoError> {
1517
use crate::stream::Stream;
1618
Ok(block_async!(self.0.chunk())?)
1719
}

0 commit comments

Comments
 (0)