Add --title, --audio, --description, --audio-url to the upload command

This commit is contained in:
Marcin Kulik
2026-03-01 10:43:04 +01:00
parent cd40c30661
commit 52313a82a6
4 changed files with 96 additions and 13 deletions

View File

@@ -32,6 +32,18 @@ pub enum Visibility {
Private,
}
#[derive(Default, Serialize)]
pub struct RecordingChangeset {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub visibility: Option<Visibility>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio_url: Option<Option<String>>,
}
#[derive(Default, Serialize)]
pub struct StreamChangeset {
#[serde(skip_serializing_if = "Option::is_none")]
@@ -66,11 +78,15 @@ pub fn get_auth_url(config: &mut Config) -> Result<Url> {
Ok(url)
}
pub async fn create_recording(path: &str, config: &mut Config) -> Result<RecordingResponse> {
pub async fn create_recording(
path: &str,
changeset: RecordingChangeset,
config: &mut Config,
) -> Result<RecordingResponse> {
let server_url = &config.get_server_url()?;
let install_id = config.get_install_id()?;
let response = create_recording_request(server_url, path, install_id)
let response = create_recording_request(server_url, install_id, path, changeset)
.await?
.send()
.await?;
@@ -94,18 +110,46 @@ pub async fn create_recording(path: &str, config: &mut Config) -> Result<Recordi
async fn create_recording_request(
server_url: &Url,
path: &str,
install_id: String,
path: &str,
changeset: RecordingChangeset,
) -> Result<RequestBuilder> {
let client = Client::new();
let mut url = server_url.clone();
url.set_path("api/v1/recordings");
let form = Form::new().file("file", path).await?;
let form = add_recording_changeset_fields(form, changeset);
let builder = client.post(url).multipart(form);
Ok(add_headers(builder, &install_id))
}
fn add_recording_changeset_fields(mut form: Form, changeset: RecordingChangeset) -> Form {
if let Some(Some(title)) = changeset.title {
form = form.text("title", title);
}
if let Some(Some(description)) = changeset.description {
form = form.text("description", description);
}
if let Some(visibility) = changeset.visibility {
let visibility = match visibility {
Visibility::Public => "public",
Visibility::Unlisted => "unlisted",
Visibility::Private => "private",
};
form = form.text("visibility", visibility);
}
if let Some(Some(audio_url)) = changeset.audio_url {
form = form.text("audio_url", audio_url);
}
form
}
pub async fn list_user_streams(prefix: &str, config: &mut Config) -> Result<Vec<StreamResponse>> {
let server_url = config.get_server_url()?;
let install_id = config.get_install_id()?;

View File

@@ -408,7 +408,7 @@ pub struct Stream {
/// Set the visibility level for the stream (applies to remote streaming with --remote). Public streams appear in listings and on your profile page. Unlisted streams are accessible via direct URL but don't appear in listings. Private streams are only accessible to the owner.
#[arg(long, value_enum, help = "Visibility level", long_help)]
pub visibility: Option<StreamVisibility>,
pub visibility: Option<Visibility>,
/// Specify URL of a live audio stream (e.g., Icecast MP3/OGG) to synchronize with the terminal stream (applies to remote streaming with --remote). When set, viewers can listen to audio commentary while watching the terminal. The audio URL is stored in the stream metadata and used by the player for synchronized playback. For example: --audio-url https://icecast.example.com/live.mp3
#[arg(
@@ -444,9 +444,9 @@ pub struct Stream {
pub server_url: Option<String>,
}
/// Visibility level for streams
/// Visibility level for uploads and streams
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum StreamVisibility {
pub enum Visibility {
Public,
Unlisted,
Private,
@@ -533,7 +533,7 @@ pub struct Session {
/// Set the visibility level for the stream (applies to remote streaming with --stream-remote). Public streams appear in listings and on your profile page. Unlisted streams are accessible via direct URL but don't appear in listings. Private streams are only accessible to the owner.
#[arg(long, value_enum, help = "Stream visibility level", long_help)]
pub visibility: Option<StreamVisibility>,
pub visibility: Option<Visibility>,
/// Specify URL of a live audio stream (e.g., Icecast MP3/OGG) to synchronize with the terminal stream (applies to remote streaming with --stream-remote). When set, viewers can listen to audio commentary while watching the terminal. The audio URL is stored in the stream metadata and used by the player for synchronized playback. For example: --audio-url https://icecast.example.com/live.mp3
#[arg(
@@ -622,6 +622,31 @@ pub struct Upload {
/// The path to the asciicast recording file to upload, in a supported asciicast format (v1, v2, or v3).
pub file: String,
/// Set a title for the recording that will be stored in the recording metadata and displayed to the viewers. For example: --title "Installing Podman on Ubuntu". This option takes precedence over the "title" field from the recording file itself.
#[arg(short, long, help = "Title of the recording", long_help)]
pub title: Option<String>,
/// Set a description for the recording. This description is displayed on the recording page and can include formatting, links, and code blocks. Useful for providing context, instructions, or documentation for viewers.
#[arg(
long,
help = "Description of the recording (Markdown supported)",
long_help
)]
pub description: Option<String>,
/// Set the visibility level for the recording. Public recordings appear in listings, search results and on your profile page. Unlisted recordings are accessible via direct URL but don't appear in listings. Private recordings are only accessible to the owner.
#[arg(long, value_enum, help = "Recording visibility level", long_help)]
pub visibility: Option<Visibility>,
/// Specify URL of an audio file (e.g., MP3/OGG) to synchronize with the terminal playback. When set, viewers can listen to audio commentary while watching the terminal. The audio URL is stored in the recording metadata and used by the player for synchronized playback. For example: --audio-url https://example.com/commentary.mp3
#[arg(
long,
value_name = "URL",
help = "Audio stream URL for synchronized playback",
long_help
)]
pub audio_url: Option<String>,
/// Specify a custom asciinema server URL for uploading to self-hosted servers. Use the base server URL (e.g., https://asciinema.example.com). Can also be set via environment variable ASCIINEMA_SERVER_URL or config file option server.url. If no server URL is configured via this option, environment variable, or config file, you will be prompted to choose one (defaulting to asciinema.org), which will be saved as a default.
#[arg(long, value_name = "URL", help = "asciinema server URL", long_help)]
pub server_url: Option<String>,

View File

@@ -16,7 +16,7 @@ use url::Url;
use crate::api::{self, StreamChangeset, StreamResponse, Visibility};
use crate::asciicast::{self, Version};
use crate::cli::{self, Format, RelayTarget, StreamVisibility};
use crate::cli::{self, Format, RelayTarget};
use crate::config::{self, Config};
use crate::encoder::{AsciicastV2Encoder, AsciicastV3Encoder, Encoder, RawEncoder, TextEncoder};
use crate::file_writer::FileWriter;
@@ -340,9 +340,9 @@ impl cli::Session {
};
let visibility = self.visibility.map(|v| match v {
StreamVisibility::Public => Visibility::Public,
StreamVisibility::Unlisted => Visibility::Unlisted,
StreamVisibility::Private => Visibility::Private,
cli::Visibility::Public => Visibility::Public,
cli::Visibility::Unlisted => Visibility::Unlisted,
cli::Visibility::Private => Visibility::Private,
});
let changeset = StreamChangeset {

View File

@@ -1,7 +1,7 @@
use anyhow::Result;
use tokio::runtime::Runtime;
use crate::api;
use crate::api::{self, RecordingChangeset};
use crate::asciicast;
use crate::cli;
use crate::config::Config;
@@ -14,7 +14,21 @@ impl cli::Upload {
async fn do_run(self) -> Result<()> {
let mut config = Config::new(self.server_url.clone())?;
let _ = asciicast::open_from_path(&self.file)?;
let response = api::create_recording(&self.file, &mut config).await?;
let visibility = self.visibility.map(|v| match v {
cli::Visibility::Public => api::Visibility::Public,
cli::Visibility::Unlisted => api::Visibility::Unlisted,
cli::Visibility::Private => api::Visibility::Private,
});
let changeset = RecordingChangeset {
title: self.title.map(Some),
description: self.description.map(Some),
visibility,
audio_url: self.audio_url.map(Some),
};
let response = api::create_recording(&self.file, changeset, &mut config).await?;
println!("{}", response.message.unwrap_or(response.url));
Ok(())