app
Adom Video Post-Production
Public Made by Adomby adom
AI-driven studio for finishing demo videos: review every captured clip, flag and re-record the weak ones, speed up dead air, narrate, mux and auto-level, validate the final cut, and publish straight to the wiki, driven end to end by your AI.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
//! Phase 3: publish a final video to the Adom Wiki.
//!
//! Pushes the polished demo video into the wiki page's repo via
//! `adom-wiki repo push --files <video> <org/slug> -m "<msg>"`. Binaries push
//! fine: the mp4 lands in the page's Files tab and serves at
//! `https://wiki.adom.inc/api/v1/pages/<slug>/files/<file>`, which is the URL we
//! return so callers can embed it.
//!
//! (The old `adom-wiki asset upload` pillar this used to wrap was removed; the
//! CLI no longer has an `asset` command. For versioned media,
//! `adom-wiki release upload <ref> <ver> <file>` exists, but for a page demo
//! video `repo push` is the right call.)
use std::process::Command;
const WIKI_BASE: &str = "https://wiki.adom.inc";
pub struct PublishConfig {
pub input: std::path::PathBuf,
pub page: String,
pub caption: Option<String>,
pub asset_type: String,
}
pub fn run(cfg: PublishConfig) -> Result<String, String> {
if !cfg.input.exists() {
return Err(format!(
"input video not found: {}. Hint: produce it via `adom-video-post voiceover` first.",
cfg.input.display()
));
}
// Validate page-ref shape: <owner>/<slug> (e.g. adom/adom-desktop-fusion-bridge).
let (_owner, slug) = parse_page_ref(&cfg.page)?;
// Validate file extension against the asset type.
let ext = cfg
.input
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_lowercase();
let valid_video_exts = ["webm", "mp4", "mov", "mkv"];
let valid_image_exts = ["png", "jpg", "jpeg"];
let is_video = valid_video_exts.contains(&ext.as_str());
let is_image = valid_image_exts.contains(&ext.as_str());
if cfg.asset_type == "video" && !is_video {
return Err(format!(
"input file '{}' is not a recognized video format ({:?}). Hint: pass --asset-type screenshot if uploading a still image.",
cfg.input.display(),
valid_video_exts
));
}
if cfg.asset_type == "screenshot" && !is_image {
return Err(format!(
"input file '{}' is not a recognized image format ({:?}).",
cfg.input.display(),
valid_image_exts
));
}
// Check adom-wiki is installed.
if which("adom-wiki").is_none() {
return Err(
"adom-wiki CLI not found in PATH. Hint: it ships with every Adom container. Run `which adom-wiki` to verify, or contact your container admin."
.to_string(),
);
}
let filename = cfg
.input
.file_name()
.and_then(|f| f.to_str())
.ok_or_else(|| format!("could not read filename from {}", cfg.input.display()))?;
let message = match &cfg.caption {
Some(c) => format!("Add {} demo {}: {}", cfg.asset_type, filename, c),
None => format!("Add {} {} via video-post publish", cfg.asset_type, filename),
};
// Build the push command: adom-wiki repo push --files <input> <page> -m "<msg>"
let mut cmd = Command::new("adom-wiki");
cmd.arg("repo").arg("push");
cmd.arg("--files").arg(&cfg.input);
cmd.args(["-m", &message]);
cmd.arg(&cfg.page);
println!("uploading {} to wiki page {}...", cfg.input.display(), cfg.page);
let result = cmd.output().map_err(|e| format!("adom-wiki spawn failed: {}", e))?;
if !result.status.success() {
let stderr = String::from_utf8_lossy(&result.stderr);
let stdout = String::from_utf8_lossy(&result.stdout);
return Err(format!(
"adom-wiki repo push failed:\n{}{}",
stdout, stderr
));
}
// The served URL is deterministic: <base>/api/v1/pages/<slug>/files/<file>.
Ok(served_file_url(slug, filename))
}
/// Parse a page ref of the form `<owner>/<slug>` and return (owner, slug).
/// The served file URL keys off the slug only (the last path segment).
fn parse_page_ref(page: &str) -> Result<(&str, &str), String> {
let mut parts = page.splitn(2, '/');
let owner = parts.next().unwrap_or("");
let slug = parts.next().unwrap_or("");
if owner.is_empty() || slug.is_empty() || slug.contains('/') {
return Err(format!(
"invalid --page '{}': expected format <owner>/<slug> like adom/adom-desktop-fusion-bridge, adom/symbol-creator. Hint: browse pages at {}.",
page, WIKI_BASE
));
}
Ok((owner, slug))
}
/// Build the URL the pushed file serves at, e.g.
/// https://wiki.adom.inc/api/v1/pages/adom-desktop-fusion-bridge/files/library-demo.mp4
fn served_file_url(slug: &str, filename: &str) -> String {
format!("{}/api/v1/pages/{}/files/{}", WIKI_BASE, slug, filename)
}
fn which(cmd: &str) -> Option<std::path::PathBuf> {
let path = std::env::var_os("PATH")?;
for dir in std::env::split_paths(&path) {
let candidate = dir.join(cmd);
if candidate.is_file() {
return Some(candidate);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_owner_and_slug() {
let (owner, slug) = parse_page_ref("adom/adom-desktop-fusion-bridge").unwrap();
assert_eq!(owner, "adom");
assert_eq!(slug, "adom-desktop-fusion-bridge");
}
#[test]
fn rejects_missing_slug() {
assert!(parse_page_ref("adom").is_err());
assert!(parse_page_ref("").is_err());
assert!(parse_page_ref("/slug").is_err());
assert!(parse_page_ref("a/b/c").is_err());
}
#[test]
fn builds_served_url_from_slug_only() {
assert_eq!(
served_file_url("adom-desktop-fusion-bridge", "library-demo.mp4"),
"https://wiki.adom.inc/api/v1/pages/adom-desktop-fusion-bridge/files/library-demo.mp4"
);
}
}