odl 2.3.1

flexible download library and CLI intended to be fast, reliable, and easy to use.
Documentation
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;
}