syntax = "proto3";
package odl.download_metadata;
// what we want to do is to know how many parts initially were aiming for
// in case user increases the connection count compared to the initial settings,
// we need to increase the parts as well,
// but if user decreases the connection count, we need to keep the parts count the same
// we will just finish downloading each part and then concatenate them at the end
message DownloadMetadata {
string url = 1;
string filename = 2;
string save_dir = 3;
bool is_resumable = 4;
bool use_server_time = 5;
optional int64 last_modified = 6;
optional string last_etag = 7;
// final size reported by server
optional uint64 size = 8;
// file checksums provided by server
repeated FileChecksum checksums = 9;
bool requires_auth = 10;
bool requires_basic_auth = 11;
map<string, string> headers = 12;
uint64 max_connections = 13;
map<string, PartDetails> parts = 14; // ulid -> PartDetails
bool finished = 15;
// headers of the last successful probe, kept for display purposes only.
// credential-bearing headers are dropped and the total is capped, so this
// is a filtered view rather than what the server literally sent.
// repeated (not a map) to preserve server order and repeated header names.
repeated ResponseHeader response_headers = 16;
// when those headers were observed (unix seconds). without it, consumers
// cannot tell a probe from a minute ago from one from last month.
optional int64 response_headers_probed_at = 17;
// which engine performs the transfer. absent (the proto3 default of 0) is
// `http_multipart`, which is what every metadata file written before this
// field existed used — so old files decode correctly without migration.
DownloadEngine engine = 18;
// engine-specific state. `http_multipart` needs none: the fields above
// already are its state. absent means "no engine-specific state".
oneof engine_details {
YtdlpDetails ytdlp_details = 19;
// TorrentDetails torrent_details = 20; // planned
}
}
// Named after what the engine does rather than a version number: a version
// says nothing about behaviour, and a second HTTP strategy would be a
// sibling rather than a successor.
//
// Value 0 cannot be an `unspecified` sentinel here — metadata written before
// the field existed decodes as 0 and means `http_multipart`.
enum DownloadEngine {
http_multipart = 0;
ytdlp = 1;
// torrent = 2; // planned
// The URL has not been evaluated yet, so which engine will handle it is
// still unknown. Produced by a cheap, network-free inspection so a queue can
// hold a pasted link without waiting; evaluating it later replaces this with
// a real engine. Nothing downloadable: attempting to transfer one is an
// error rather than a guess.
unresolved = 3;
}
// State needed to safely resume a download delegated to `yt-dlp`.
message YtdlpDetails {
// page URL the media was extracted from. `DownloadMetadata.url` holds the
// same value: extracted media URLs are short-lived and signed, so the page
// URL is what we re-extract from on every resume.
string source_url = 1;
// format selected on the first run. resuming under a different format would
// append incompatible bytes to an existing `.part` and yield a corrupt file
// with a zero exit code, so this is pinned rather than re-selected.
string format_id = 2;
string extractor = 3;
string title = 4;
// true when `size` came from yt-dlp's estimate rather than an exact value.
bool size_is_approx = 5;
// Enough of the chosen format to describe it to a person after the fact.
// The format list is gone by then, and "137+251" is not a quality anyone
// recognises. Stored structured rather than as a rendered string so a UI
// can phrase it in its own language.
optional uint32 height = 6;
optional double fps = 7;
optional double bitrate_kbps = 8;
// The chosen format carries sound but no picture.
bool audio_only = 9;
// the extractor's own id for the media. `source_url` cannot identify it:
// one video has many URL spellings, and comparing them as strings turns a
// resume into a conflict. empty for downloads stored before this field
// existed, and for extractors that report no id, so readers must fall back
// to `source_url` rather than treat empty as "no match".
string video_id = 10;
}
message ResponseHeader {
string name = 1;
string value = 2;
}
message PartDetails {
uint64 offset = 1;
uint64 size = 2;
string ulid = 3; // file name = `ulid` + `'.part'`
bool finished = 4; // important for failure detection (Unexpected crashes, power loss, etc.)
}
message FileChecksum {
ChecksumAlgorithm algorithm = 1;
string digest = 2;
ChecksumEncoding encoding = 3;
}
enum ChecksumAlgorithm {
sha512 = 0;
sha384 = 1;
sha256 = 2;
sha1 = 3;
md5 = 4;
}
enum ChecksumEncoding {
hex = 0;
base64 = 1;
}