How do I tell std::io::copy in Rust to stop reading and finish up writing?
Asked Answered
S

1

6

I'm doing a direct download of an MP3 audio stream via Rust. As this stream is indefinite, I want to be able to cancel it early to save what I have downloaded so far. Currently, I do this by pressing CTRL + C to stop the program. This results in a stream.mp3 file I can then play back and listen to, and while this works, it isn't ideal.

Given the following code, how could I programmatically stop io::copy() early and have it save the file without killing the entire program?

extern crate reqwest;

use std::io;
use std::fs::File;

// Note that this is a direct link to the stream, not a webpage with HTML and a stream
const STREAM_URL: &str = "http://path.to/stream";

fn main() {
    let mut response = reqwest::get(STREAM_URL)
        .expect("Failed to request mp3 stream");
    let mut output = File::create("stream.mp3")
        .expect("Failed to create file!");
    io::copy(&mut response, &mut output)
        .expect("Failed to copy mp3 stream to file");
}
Stale answered 12/12, 2019 at 14:34 Comment(2)
You can't do that with io::copy. io::copy's job is literally to copy the entire thing no matter what it is. You'd have to tell reqwest to end the downloading somehow, but given that reqwest is mostly a convenience library around hyper to download things simply, I also doubt it would have an API for this. Your next move would then to use hyper directly, where cancelling a stream is just a matter of dropping its future.Krell
I also don't know much about the MP3 format, but I doubt you can just split a file at any point and get a valid MP3 file out of that. You'd probably get something that your player can play, because they are used to bad files, but it wouldn't necessarily be valid.Krell
S
4

As the comments said, io::copy is a convenience function to read a Reader in full and write it's content to a Writer without stoping in between; it's used for when you do not care about intermediate state but just want the entire thing to be shipped from the reader to the writer.

If you just want the first few Kb from the response, you can use io::Read::take, which limits the Reader to whatever limit you specified. It will return a new Reader which you can pass to io::copy.

And yes, you can cut an MP3-file at arbitrary positions. It is a framed format and while you will most likely destroy the last frame, practically all mp3-decoders are able to handle this.


Something to the tune of

// `Read` needs to be in scope, so .take() is available on a `io::Read`
use std::io::Read;
use std::io;
use std::fs::File;

fn main() {
    let mut response = reqwest::get(STREAM_URL)
        .expect("Failed to request mp3 stream")
        .take(100*1024);  // Since `Response` is `Read`, we can `take` from it.
    let mut output = File::create("stream.mp3")
        .expect("Failed to create file!");
    // `response` is a limited reader, so it will only read 100kb before
    // returning EOF, signaling a successful completion to `copy`.
    io::copy(&mut response, &mut output)
        .expect("Failed to copy mp3 stream to file");
}
Seedbed answered 12/12, 2019 at 17:42 Comment(1)
Do you think you could add a code example of how you would implement this? Somewhat new to Rust.Stale

© 2022 - 2024 — McMap. All rights reserved.