diff --git a/.changeset/create-next-steps.md b/.changeset/create-next-steps.md new file mode 100644 index 00000000..9c592eb4 --- /dev/null +++ b/.changeset/create-next-steps.md @@ -0,0 +1,5 @@ +--- +"@bunny.net/cli": patch +--- + +`bunny stream library create` now offers to link the directory, save the library ID and API key to `.env`, and import or upload videos, then prints next steps. `storage zones create`, `sites create`, and `pz create` link to the matching examples. diff --git a/.changeset/stream-commands.md b/.changeset/stream-commands.md new file mode 100644 index 00000000..e751526b --- /dev/null +++ b/.changeset/stream-commands.md @@ -0,0 +1,5 @@ +--- +"@bunny.net/cli": minor +--- + +Add `bunny stream` commands to manage Stream video libraries and videos: `stream libraries` (create, list, show, update, credentials, player and security settings), `stream videos` (upload, fetch, update, thumbnails, stats, embed URLs, encode, transcribe, and Smart Generate), `stream collections`, `stream captions`, `stream link`, and `stream regions`. diff --git a/README.md b/README.md index 9da1354c..0aae1d31 100644 --- a/README.md +++ b/README.md @@ -85,28 +85,28 @@ bun ny sites domains add example.com # attach a custom production domain bun ny sites ssl --no-force-ssl # stop forcing HTTPS on the site's b-cdn.net system host bun ny sites open # open the site's live URL in the browser bun ny sites ci init # add a GitHub Actions workflow (push to main goes live) -bun ny stream library list # list Stream video libraries (videos, storage, traffic, replication regions) -bun ny stream library create my-library # create a video library in DE, replicated to SG, LA and NY by default (omit the name to be prompted; --name also works) -bun ny stream library create my-library --replication-regions NY,SG # replicate to New York and Singapore instead (DE is always the main region; DE alone means no replication) -bun ny stream library show my-library # show one library (accepts a name or ID; omit it to use the linked library, or to pick interactively when nothing is linked). API keys are never printed here, in any output format -bun ny stream library update my-library --resolutions 720p,1080p # edit library settings; omit the flags to edit interactively (encoding tier, codecs, transcribing all have flags) -bun ny stream library credentials my-library --show-secret # deliberately retrieve a library's Stream API key (--read-only for the read-only key; masked without --show-secret) -bun ny stream library player update my-library --color "#FF7755" --heatmap # player settings (show/update/reset) -bun ny stream library security update my-library --embed-token --allow-referrer example.com # security settings (show/update/reset-token) -bun ny stream library delete my-library # delete a library and all of its videos (--force skips the confirmation, and is required non-interactively) -bun ny stream library link my-library # link the directory to a library so video commands can omit it (bun ny stream library unlink removes the link) -bun ny stream video upload ./video.mp4 # upload a local video to the linked library (--title sets the title; files over 2 GB upload resumably via TUS, retried and resumed automatically) -bun ny stream video fetch https://example.com/video.mp4 --lib 12345 # let bunny.net fetch the video server side (--header "Name: value" for an origin that needs auth) -bun ny stream video list # list the videos in the linked library (ID, title, status, size, length, views, upload date) -bun ny stream video show 1a2b3c4d-... # show one video by GUID, including its Direct Play URL -bun ny stream video thumbnail 1a2b3c4d-... --file ./thumb.jpg # set a thumbnail (--url has bunny.net download one instead) -bun ny stream video stats 1a2b3c4d-... # views and watch time for one video (--heatmap, --play-data for the other views) -bun ny stream video embed 1a2b3c4d-... # print the player embed URL, signed when the library requires embed view tokens -bun ny stream video cleanup 1a2b3c4d-... --non-configured --dry-run # preview deleting renditions the library no longer configures -bun ny stream collection list # list a library's collections (create/show/rename/delete too; videos join one with --collection, and deleting a collection deletes the videos inside it) -bun ny stream caption add 1a2b3c4d-... en --file ./captions.vtt # upload your own caption file for one language -bun ny stream transcribe 1a2b3c4d-... --languages en,de # paid: transcribe the audio into captions ($0.10 per language-minute, counting the source-language transcript and each target language) -bun ny stream smart 1a2b3c4d-... --title --chapters # paid: generate a title and chapters from the transcript (offers to transcribe first if the video has no captions) +bun ny stream libraries list # list Stream video libraries (videos, storage, traffic, replication regions) +bun ny stream libraries create my-library # create a video library in DE, replicated to SG, LA and NY by default (omit the name to be prompted) +bun ny stream libraries create my-library --replication-regions NY,SG # replicate to New York and Singapore instead (DE is always the main region; DE alone means no replication) +bun ny stream libraries show my-library # show one library (accepts a name or ID; omit it to use the linked library, or to pick interactively when nothing is linked). API keys are never printed here, in any output format +bun ny stream libraries update my-library --resolutions 720p,1080p # edit library settings; omit the flags to edit interactively (encoding tier, codecs, transcribing all have flags) +bun ny stream libraries credentials my-library --show-secret # deliberately retrieve a library's Stream API key (--read-only for the read-only key; masked without --show-secret) +bun ny stream libraries player update my-library --color "#FF7755" --heatmap # player settings (show/update/reset) +bun ny stream libraries security update my-library --embed-token --allow-referrer example.com # security settings (show/update; show --rotate resets the token key) +bun ny stream libraries delete my-library # delete a library and all of its videos (--force skips the confirmation, and is required non-interactively) +bun ny stream link my-library # link the directory to a library so video commands can omit it (also `libraries link`; bun ny stream unlink removes the link) +bun ny stream videos upload ./video.mp4 # upload a local video to the linked library (--title sets the title; files over 2 GB upload resumably via TUS, retried and resumed automatically) +bun ny stream videos fetch https://example.com/video.mp4 --lib 12345 # let bunny.net fetch the video server side (--header "Name: value" for an origin that needs auth) +bun ny stream videos list # list the videos in the linked library (ID, title, status, size, length, views, upload date) +bun ny stream videos show 1a2b3c4d-... # show one video by GUID, including its Direct Play URL +bun ny stream videos thumbnail 1a2b3c4d-... --file ./thumb.jpg # set a thumbnail (--url has bunny.net download one instead) +bun ny stream videos stats 1a2b3c4d-... # views and watch time for one video (--heatmap, --play-data for the other views) +bun ny stream videos embed 1a2b3c4d-... # print the player embed URL, signed when the library requires embed view tokens +bun ny stream videos cleanup 1a2b3c4d-... --non-configured --dry-run # preview deleting renditions the library no longer configures +bun ny stream collections list # list a library's collections (create/show/update/delete too; videos join one with --collection, and deleting a collection deletes the videos inside it) +bun ny stream videos captions add ./captions.vtt 1a2b3c4d-... en # upload your own caption file for one language +bun ny stream videos transcribe 1a2b3c4d-... --target-languages en,de # paid: transcribe the audio into captions ($0.10 per language-minute, counting the source-language transcript and each target language) +bun ny stream videos smart 1a2b3c4d-... --title --chapters # paid: generate a title and chapters from the transcript (offers to transcribe first if the video has no captions) ``` Every deploy is published as the live site. Deploys are immutable under their own ID, so `bun ny sites deployments publish` rolls back to any earlier one without re-uploading. Preconfigure the `sites` block in `bunny.jsonc` (`name`, `build`, `dir`, `spa`) so a deploy needs no flags: `bun ny sites deploy --build`. `bun ny sites ci init` writes the same `build` and `dir` into the generated workflow. See [`examples/sites/`](examples/sites/) for ready-to-copy configs (Vite, Astro, Next.js static export, Hugo, plain HTML, and a combined app + site file). diff --git a/packages/cli/README.md b/packages/cli/README.md index 0f665dbf..4fc385e2 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1116,111 +1116,115 @@ Every deploy publishes: the files land in an immutable `deploys//` directory ### `bunny stream` -> **Experimental**: hidden from `--help` and the landing page while it stabilizes. +> **Preview**: `bunny stream` is in public preview; commands and flags may still change. -Manage bunny.net Stream through four resource groups: **`bunny stream library`** (the library itself: create, list, inspect, update, delete, plus its **`player`** and **`security`** settings; aliases `libraries`, `lib`), **`bunny stream video`** (the videos inside one library; alias `videos`), **`bunny stream collection`** (groups of videos within a library; alias `collections`), and **`bunny stream caption`** (caption files on one video; alias `captions`). Three paid operations live alongside them: **`bunny stream encode`**, **`bunny stream transcribe`**, and **`bunny stream smart`**. Library management uses the account API key on the core API; each library also carries its own Stream API key for the video-level API, which the CLI never prints unless you ask for it with `credentials`. Video commands use that per-library key, resolved automatically from the library, so there is nothing extra to configure. **`bunny stream import`** moves an existing video library in from another platform; see [below](#bunny-stream-import). +Manage bunny.net Stream through three resource groups: **`bunny stream libraries`** (the library itself: create, list, inspect, update, delete, plus its **`player`** and **`security`** settings; aliases `library`, `lib`), **`bunny stream videos`** (the videos inside one library, their **`captions`** files, and the paid `encode`, `transcribe`, and `smart`; alias `video`), and **`bunny stream collections`** (groups of videos within a library; alias `collection`). **`bunny stream link`** and **`unlink`** mirror `libraries link` and `unlink`, and **`bunny stream regions`** lists the region codes a library can replicate to. Library management uses the account API key on the core API; each library also carries its own Stream API key for the video-level API, which the CLI never prints unless you ask for it with `credentials`. Video commands use that per-library key, resolved automatically from the library, so there is nothing extra to configure. **`bunny stream import`** moves an existing video library in from another platform; see [below](#bunny-stream-import). -`library` commands take the library as an optional positional (name or numeric ID); every other stream command takes it as the `--lib`/`--library` flag, since their positional is the video GUID, the collection ID, or a local file path. When the library is omitted it resolves from the directory's linked library (`bunny stream library link`, stored in `.bunny/stream.json`), then an interactive picker, which offers to link the directory to the picked library (destructive commands never offer it). Non-interactive runs (`--output json`, no TTY, or `--force`) error with a hint instead of prompting: pass a library or link the directory. A `video` or `collection` command with no ID behaves the same way, offering a picker interactively and erroring otherwise. `caption` is the exception: both of its commands take the video GUID as a required positional, because a language argument follows it. +`libraries` commands take the library as an optional positional (name or numeric ID); every other stream command takes it as the `--lib`/`--library` flag, since their positional is the video GUID, the collection ID, or a local file path. When the library is omitted it resolves from the directory's linked library (`bunny stream link`, stored in `.bunny/stream.json`), then an interactive picker, which offers to link the directory to the picked library (destructive commands never offer it). Non-interactive runs (`--output json`, no TTY, or `--force`) error with a hint instead of prompting: pass a library or link the directory. A `videos`, `collections`, or `videos captions` command with no ID behaves the same way, offering a picker interactively and erroring otherwise. `videos captions add [video] [lang]` then asks for the language code, and `videos captions remove [video] [lang]` offers the video's caption languages to pick from; unattended runs need the video and the language. -There is deliberately no `video create`: a video is added either by uploading a local file with `video upload`, or by handing bunny.net a URL to download with `video fetch`. +After creating a library, `libraries create` offers to link the directory, to save `BUNNY_STREAM_LIBRARY_ID` and `BUNNY_STREAM_API_KEY` to `.env` (the names the [Stream examples](https://github.com/BunnyWay/examples/tree/main/stream) read), and to add videos straight away by importing from another platform or uploading a file. `--link`/`--no-link` and `--save-env`/`--no-save-env` answer the first two without prompting, and `--no-add-videos` skips the third; non-interactive runs skip anything not set by a flag. + +There is deliberately no `videos create`: a video is added either by uploading a local file with `videos upload`, or by handing bunny.net a URL to download with `videos fetch`. ```bash # Libraries -bunny stream library list # ID, name, videos, storage, traffic, replication regions -bunny stream library create my-library # interactive: prompts for the name when omitted -bunny stream library create --name my-library # the name also takes a flag -bunny stream library create my-library # stored in DE (the fixed main region) and replicated to SG, LA, NY by default -bunny stream library create my-library --replication-regions NY,SG # DE + NY, SG instead; pass DE alone for no replication -bunny stream library create my-library --encoding-tier premium --codecs x264,vp9 # premium encoding with extra codecs -bunny stream library show my-library # details; API keys are never printed here, in any output format -bunny stream library update my-library # interactive: edit name, resolutions, transcribing -bunny stream library update my-library --resolutions 720p,1080p # set just the enabled resolutions -bunny stream library update my-library --transcribing --transcribing-languages en,de -bunny stream library update my-library --keep-original --mp4-fallback --content-tagging # encoding settings -bunny stream library update my-library --add-replication-regions SYD # add-only: regions can't be removed later -bunny stream library credentials my-library # library ID + API key, masked by default -bunny stream library credentials my-library --show-secret --read-only # reveal the read-only key -bunny stream library credentials my-library --rotate # reset the API key (confirms first), then show the new one +bunny stream libraries list # ID, name, videos, storage, traffic, replication regions +bunny stream libraries create my-library # interactive: prompts for the name when omitted +bunny stream libraries create my-library # stored in DE (the fixed main region) and replicated to SG, LA, NY by default +bunny stream libraries create my-library --replication-regions NY,SG # DE + NY, SG instead; pass DE alone for no replication +bunny stream libraries create my-library --encoding-tier premium --codecs x264,vp9 # premium encoding with extra codecs +bunny stream libraries create my-library --link --save-env # link this directory and write BUNNY_STREAM_LIBRARY_ID and BUNNY_STREAM_API_KEY to .env +bunny stream libraries show my-library # details; API keys are never printed here, in any output format +bunny stream libraries update my-library # interactive: edit name, resolutions, transcribing +bunny stream libraries update my-library --resolutions 720p,1080p # set just the enabled resolutions +bunny stream libraries update my-library --transcribing --transcribing-languages en,de +bunny stream libraries update my-library --keep-original --mp4-fallback --content-tagging # encoding settings +bunny stream libraries update my-library --add-replication-regions SYD # add-only: regions can't be removed later +bunny stream libraries credentials my-library # library ID + API key, masked by default +bunny stream libraries credentials my-library --show-secret --read-only # reveal the read-only key +bunny stream libraries credentials my-library --rotate # reset the API key (confirms first), then show the new one +bunny stream libraries credentials my-library --save-env # write BUNNY_STREAM_LIBRARY_ID and BUNNY_STREAM_API_KEY to .env +bunny stream regions # region codes: DE is the fixed main region, SG, LA, NY the default replicas # Player and security settings (per library) -bunny stream library player show my-library -bunny stream library player update my-library --color "#FF7755" --speeds 0.5,1.0,1.15,2.0 --heatmap -bunny stream library player update my-library --no-controls # every player control off -bunny stream library player reset my-library # back to the dashboard defaults; confirms first -bunny stream library security show my-library # domains, tokens, direct play, DRM; token key masked -bunny stream library security update my-library --embed-token --block-direct-access --allow-referrer example.com -bunny stream library security update my-library --drm-basic # MediaCage Basic DRM -bunny stream library security reset-token my-library # rotate the token key; signed URLs stop working -bunny stream library delete my-library # confirms with the video count; --force skips it - -# Link the working directory to a library so video commands can omit it -bunny stream library link my-library -bunny stream library unlink +bunny stream libraries player show my-library +bunny stream libraries player update my-library --color "#FF7755" --speeds 0.5,1.0,1.15,2.0 --heatmap +bunny stream libraries player update my-library --no-controls # every player control off +bunny stream libraries player reset my-library # back to the dashboard defaults; confirms first +bunny stream libraries security show my-library # domains, tokens, direct play, DRM; token key masked +bunny stream libraries security update my-library --embed-token --block-direct-access --allow-referrer example.com +bunny stream libraries security update my-library --drm-basic # MediaCage Basic DRM +bunny stream libraries security show my-library --rotate # reset the token key (confirms first); signed URLs stop working +bunny stream libraries delete my-library # confirms with the video count; --force skips it + +# Link the working directory to a library so video commands can omit it (also `libraries link` / `unlink`) +bunny stream link my-library +bunny stream unlink # Adding videos: upload a local file, or let bunny.net fetch a URL -bunny stream video upload ./video.mp4 # linked library -bunny stream video upload ./video.mp4 --lib 12345 --title "Launch demo" -bunny stream video upload ./video.mp4 --collection 8a7b6c5d-... # straight into a collection -bunny stream video fetch https://example.com/video.mp4 --lib 12345 -bunny stream video fetch https://example.com/video.mp4 --header "Authorization: Bearer abc" - -# Videos within a library (GUIDs come from `video list`) -bunny stream video list # ID, title, status, size, length, views, upload date -bunny stream video list --lib 12345 --search launch # a specific library, filtered by title -bunny stream video list --collection 8a7b6c5d-... # only one collection -bunny stream video show 1a2b3c4d-... # details, including the caption languages and the Direct Play URL -bunny stream video update 1a2b3c4d-... --title "Launch demo" # prompts for the title when omitted -bunny stream video update 1a2b3c4d-... --chapters '[{"title":"Intro","start":0,"end":30}]' -bunny stream video thumbnail 1a2b3c4d-... --file ./thumb.jpg # or --url for bunny.net to download it -bunny stream video resolutions 1a2b3c4d-... # what is configured, encoded, and stored -bunny stream video cleanup 1a2b3c4d-... --non-configured --dry-run # preview a rendition cleanup -bunny stream video stats 1a2b3c4d-... # views and watch time (--heatmap, --play-data) -bunny stream video embed 1a2b3c4d-... --expires 1h # player embed URL, signed when embed tokens are on -bunny stream video delete 1a2b3c4d-... # confirms first; --force skips it +bunny stream videos upload ./video.mp4 # linked library +bunny stream videos upload ./video.mp4 --lib 12345 --title "Launch demo" +bunny stream videos upload ./video.mp4 --collection 8a7b6c5d-... # straight into a collection +bunny stream videos fetch https://example.com/video.mp4 --lib 12345 +bunny stream videos fetch https://example.com/video.mp4 --header "Authorization: Bearer abc" + +# Videos within a library (GUIDs come from `videos list`) +bunny stream videos list # ID, title, status, size, length, views, upload date +bunny stream videos list --lib 12345 --search launch # a specific library, filtered by title +bunny stream videos list --collection 8a7b6c5d-... # only one collection +bunny stream videos show 1a2b3c4d-... # details, including the caption languages and the Direct Play URL +bunny stream videos update 1a2b3c4d-... --title "Launch demo" # prompts for the title when omitted +bunny stream videos update 1a2b3c4d-... --chapters '[{"title":"Intro","start":0,"end":30}]' +bunny stream videos thumbnail 1a2b3c4d-... --file ./thumb.jpg # or --url for bunny.net to download it +bunny stream videos resolutions 1a2b3c4d-... # what is configured, encoded, and stored +bunny stream videos cleanup 1a2b3c4d-... --non-configured --dry-run # preview a rendition cleanup +bunny stream videos stats 1a2b3c4d-... # views and watch time (--heatmap, --play-data) +bunny stream videos embed 1a2b3c4d-... --expires 1h # player embed URL, signed when embed tokens are on +bunny stream videos delete 1a2b3c4d-... # confirms first; --force skips it # Collections group a library's videos -bunny stream collection list # ID, name, videos, size -bunny stream collection create --name Tutorials # the name also takes a positional -bunny stream collection show 8a7b6c5d-... -bunny stream collection rename 8a7b6c5d-... Guides -bunny stream collection delete 8a7b6c5d-... # deletes the videos inside it too; confirms first +bunny stream collections list # ID, name, videos, size +bunny stream collections create Tutorials # interactive: prompts for the name when omitted +bunny stream collections show 8a7b6c5d-... +bunny stream collections update 8a7b6c5d-... --name Guides +bunny stream collections delete 8a7b6c5d-... # deletes the videos inside it too; confirms first # Caption files on one video (manual captions, not the paid transcription) -bunny stream caption add 1a2b3c4d-... en --file ./captions.vtt --label "English" -bunny stream caption delete 1a2b3c4d-... en # confirms first; --force skips it +bunny stream videos captions add ./captions.vtt 1a2b3c4d-... en --label "English" +bunny stream videos captions remove 1a2b3c4d-... en # confirms first; --force skips it # Paid operations -bunny stream encode enable --codecs x264,vp9 # switch the library to premium encoding -bunny stream encode reencode 1a2b3c4d-... # re-encode one video with the current settings -bunny stream transcribe 1a2b3c4d-... --languages en,de # $0.10 per language-minute -bunny stream smart 1a2b3c4d-... --title --chapters # offers to transcribe first if the video has no captions +bunny stream libraries update my-library --encoding-tier premium --codecs x264,vp9 # switch the library to premium encoding +bunny stream videos encode 1a2b3c4d-... # re-encode one video with the current settings +bunny stream videos transcribe 1a2b3c4d-... --target-languages en,de # $0.10 per language-minute +bunny stream videos smart 1a2b3c4d-... --title --chapters # offers to transcribe first if the video has no captions ``` Deleting a library deletes all of its videos, and deleting the linked library also removes the stale `.bunny/stream.json`. `--force` is required for non-interactive deletes, cleanups, and unlinks; without it, a run that cannot prompt exits with an error instead of hanging. On destructive and paid commands `--force` also disables the pickers, so it can never act on something you did not name. -`library create` and `library update` share the encoding and transcribing flags, and `update` sends only the fields you pass, leaving every other setting untouched. With no flags at all, `update` opens a small interactive editor (name, enabled resolutions, transcribing) prefilled with the library's current values; unattended runs need at least one flag. +`libraries create` and `libraries update` share the encoding and transcribing flags, and `update` sends only the fields you pass, leaving every other setting untouched. With no flags at all, `update` opens a small interactive editor (name, enabled resolutions, transcribing) prefilled with the library's current values; unattended runs need at least one flag. A local upload happens in two steps: the video entry is created first, then the file's bytes are sent to it. If the byte upload fails, the CLI checks the video's status: an entry that never received bytes is deleted so a retry does not leave orphans behind, while anything that may already hold the upload (including a status the CLI could not read) is kept, with a warning naming its GUID, since a lost response does not mean the bytes were rejected. Encoding continues on bunny.net after the command returns, so the reported status is usually `Uploaded` or `Processing` rather than `Finished`. Files over 2 GB switch to resumable uploads (TUS) automatically, with no extra flag: the file goes up in 64 MiB chunks, and a chunk that fails is retried from the offset the server reports it actually holds, so a blip in the middle of a large upload does not restart the whole transfer. The spinner reports progress as it goes. Resume state lives only in the running command, so a cancelled upload starts over on the next run. Every other flag behaves identically on both paths. -`video fetch` uses bunny.net's server-side fetch instead: the origin is downloaded by bunny.net, not by the CLI, so nothing is transferred through your machine and the command returns as soon as the fetch is queued. Use `--header` (repeatable, `"Name: value"`) for an origin that needs authentication. That endpoint answers with a status rather than a video, so `fetch` reports no video ID or Direct Play URL: the video appears in `bunny stream video list` once it has been fetched and encoded, titled after the remote file name unless `--title` says otherwise. +`videos fetch` uses bunny.net's server-side fetch instead: the origin is downloaded by bunny.net, not by the CLI, so nothing is transferred through your machine and the command returns as soon as the fetch is queued. Use `--header` (repeatable, `"Name: value"`) for an origin that needs authentication. That endpoint answers with a status rather than a video, so `fetch` reports no video ID or Direct Play URL: the video appears in `bunny stream videos list` once it has been fetched and encoded, titled after the remote file name unless `--title` says otherwise. -`video cleanup` deletes encoded renditions, MP4 fallbacks, or the stored original, and needs at least one selector so it can never look like a no-op success. Start with `--dry-run`, which lists each rendition, MP4 file, or original that would go (worked out from the video's resolutions, since the API's dry run only returns a status), names any `--resolutions` the video doesn't have, deletes nothing, and skips the confirmation. A real run lists the same items in its confirmation, and a selection that matches nothing deletes nothing. `--output json` adds a `plan` with `items` and `notPresent`. +`videos cleanup` deletes encoded renditions, MP4 fallbacks, or the stored original, and needs at least one selector so it can never look like a no-op success. Start with `--dry-run`, which lists each rendition, MP4 file, or original that would go (worked out from the video's resolutions, since the API's dry run only returns a status), names any `--resolutions` the video doesn't have, deletes nothing, and skips the confirmation. A real run lists the same items in its confirmation, and a selection that matches nothing deletes nothing. `--output json` adds a `plan` with `items` and `notPresent`. -A video moves in or out of a collection with `video update --collection ` (an empty value clears it). Creating a collection is free, but deleting one is not just removing a label: the API deletes every video inside it as well, so the confirmation prompt names the count that is about to go. Move the videos you want to keep out of the collection first (`video update --collection ""` clears it), then delete it. `video upload` and `video fetch` can drop a video straight into a collection at ingest, and both also accept `--thumbnail-time ` to pick the main thumbnail from a frame while the video is being ingested. `video thumbnail` replaces the image afterwards, from a URL or a local file. +A video moves in or out of a collection with `videos update --collection ` (an empty value clears it). Creating a collection is free, but deleting one is not just removing a label: the API deletes every video inside it as well, so the confirmation prompt names the count that is about to go. Move the videos you want to keep out of the collection first (`videos update --collection ""` clears it), then delete it. `videos upload` and `videos fetch` can drop a video straight into a collection at ingest, and both also accept `--thumbnail-time ` to pick the main thumbnail from a frame while the video is being ingested. `videos thumbnail` replaces the image afterwards, from a URL or a local file. -`caption add` reads a local `.vtt` or `.srt`, sends it inline, and reports what the API's validator says: a rejected file lists what is wrong, and an accepted file with non-breaking issues prints them as warnings. These are captions you wrote yourself, unrelated to the paid transcription below. +`videos captions add` reads a local `.vtt` or `.srt`, sends it inline, and reports what the API's validator says: a rejected file lists what is wrong, and an accepted file with non-breaking issues prints them as warnings. These are captions you wrote yourself, unrelated to the paid transcription below. -Three commands cost money and say so before they run. `stream encode enable` switches a library to the premium encoding tier, billed per output codec per minute of encoded video, and `stream encode reencode` regenerates every output for one video at the same rate. `stream transcribe` is billed at $0.10 per language-minute of audio, counting the source-language transcript and each target language once. Before it runs, it prints an estimate: exact with `--source-language`, and a range without it, because the auto-detected spoken language adds one more language unless it matches a target. If the library transcribes automatically, `stream encode reencode` and a fresh upload both trigger a transcription on their own, and both commands warn you not to run `stream transcribe` on top, which would bill it twice. `stream smart` generates a title, description, chapters, or moments from the transcript, and needs at least one of those flags. Its `--source-language` names the caption track to read: captions made by transcription are labelled `-auto` (for example `en-auto`), uploaded caption files use the plain code, and `en` falls back to `en-auto` when that is the only English track. Without the flag, `smart` reads the video's only track or its single `-auto` transcript, and asks which to use when there are several (unattended runs stop and ask for `--source-language`) instead of letting the API take the first one. When the video has no captions yet, `smart` never transcribes silently: it shows the estimated cost (video minutes × the source language plus the library's default languages × $0.10) and asks whether to transcribe first. Saying yes sends one transcribe request carrying the same generate flags, so the fields come from the new transcript; saying no sends nothing, and `stream caption add` covers captions you already have. Unattended runs can't answer the prompt, so they stop with the cost unless `--transcribe` approves it up front. Note that `--force` on `stream transcribe` is the API's own force flag, which re-runs and overrides the library defaults, rather than a confirmation skip; on `stream smart` it only disables the pickers and never approves a transcription. +Switching a library to `--encoding-tier premium` bills each output codec per minute of encoded video, and three commands cost money and say so before they run. `stream videos encode` regenerates every output for one video at the encoding rate. `stream videos transcribe` is billed at $0.10 per language-minute of audio, counting the source-language transcript and each target language once. Before it runs, it prints an estimate: exact with `--source-language`, and a range without it, because the auto-detected spoken language adds one more language unless it matches a target. If the library transcribes automatically, `stream videos encode` and a fresh upload both trigger a transcription on their own, and both commands warn you not to run `stream videos transcribe` on top, which would bill it twice. `stream videos smart` generates a title, description, chapters, or moments from the transcript, and needs at least one of those flags. Its `--source-language` names the caption track to read: captions made by transcription are labelled `-auto` (for example `en-auto`), uploaded caption files use the plain code, and `en` falls back to `en-auto` when that is the only English track. Without the flag, `smart` reads the video's only track or its single `-auto` transcript, and asks which to use when there are several (unattended runs stop and ask for `--source-language`) instead of letting the API take the first one. When the video has no captions yet, `smart` never transcribes silently: it shows the estimated cost (video minutes × the source language plus the library's default languages × $0.10) and asks whether to transcribe first. Saying yes sends one transcribe request carrying the same generate flags, so the fields come from the new transcript; saying no sends nothing, and `stream videos captions add` covers captions you already have. Unattended runs can't answer the prompt, so they stop with the cost unless `--transcribe` approves it up front. `--overwrite` on `stream videos transcribe` overwrites the existing transcription and overrides the library defaults (it is sent as the API's force flag), and disables the pickers. On `stream videos smart`, `--force` only disables the pickers and never approves a transcription. Every command that operates inside a library accepts `--lib ` (alias `--library`) and falls back to the linked directory, so none of the tables below repeat it. -Library flags (`bunny stream library ...`): +Library flags (`bunny stream libraries ...`): | Flag | Commands | Description | | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------ | -| `--name` | `create`, `update` | Library name; on `create` it is interchangeable with the positional | +| `--name` | `update` | New library name (`create` takes the name as its positional) | | `--replication-regions` | `create` | Regions besides DE, the fixed main region; default `SG,LA,NY`; `DE` alone means none | | `--add-replication-regions` | `update` | Add regions later; add-only, since regions can't be removed | | `--mp4-fallback`, `--early-play`, `--keep-original`, `--multi-audio` (and `--no-` forms) | `create`, `update` | Encoding settings; `--early-play` needs originals and exposes them | @@ -1235,9 +1239,11 @@ Library flags (`bunny stream library ...`): | `--transcribing-title`, `--transcribing-description`, `--transcribing-chapters`, `--transcribing-moments` | `create`, `update` | Generate each field from the transcript | | `--read-only`, `--show-secret` | `credentials` | Use the read-only key; reveal the key (masked by default) | | `--rotate` | `credentials` | Reset the key (or the read-only key with `--read-only`), then show the new one | +| `--save-env` / `--no-save-env` | `create`, `credentials` | Save `BUNNY_STREAM_LIBRARY_ID` and the shown API key to `.env` without asking, or skip the offer | +| `--no-add-videos` | `create` | Skip the offer to import or upload videos | | `--force`, `-f` | `update`, `unlink`, `delete`, `credentials --rotate` | Skip the prompts (required when there is no TTY to answer them) | -Player flags (`bunny stream library player update`): +Player flags (`bunny stream libraries player update`): | Flag | Description | | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | @@ -1252,7 +1258,7 @@ Player flags (`bunny stream library player update`): `player reset` restores the dashboard defaults: `en`, Rubik, `#FF7755`, white captions on black at 20, the default controls and speeds. It confirms first; `--force` skips that. -Security flags (`bunny stream library security update`): +Security flags (`bunny stream libraries security update`): | Flag | Description | | ----------------------------------------------- | ------------------------------------------------------------------------ | @@ -1263,9 +1269,9 @@ Security flags (`bunny stream library security update`): | `--embed-token`, `--cdn-token`, `--token-ip` | Embed view tokens, CDN tokens, and binding CDN tokens to the viewer's IP | | `--drm-basic` | MediaCage Basic DRM; libraries on Enterprise DRM are refused | -All boolean flags take `--no-` forms. `security update` sends the settings in one request, then each domain change on its own; changes already in place are skipped, and if one fails the command stops, reports what landed, and exits non-zero, so re-running the same command is safe. CDN token state and the token key are read from the library's linked Pull Zone. `video embed` signs with that key when embed tokens are on. +All boolean flags take `--no-` forms. `security update` sends the settings in one request, then each domain change on its own; changes already in place are skipped, and if one fails the command stops, reports what landed, and exits non-zero, so re-running the same command is safe. CDN token state and the token key are read from the library's linked Pull Zone. `security show --rotate` resets the token key used for both CDN and embed view tokens, after a confirmation that `--force` skips; every signed URL issued with the old key stops working. `videos embed` signs with that key when embed tokens are on. -Video flags (`bunny stream video ...`): +Video flags (`bunny stream videos ...`): | Flag | Commands | Description | | ---------------------------------------------------------------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------- | @@ -1284,25 +1290,24 @@ Video flags (`bunny stream video ...`): Collection and caption flags: -| Flag | Commands | Description | -| ------------------- | ---------------------------------------- | ---------------------------------------------------------------------------- | -| `--name` | `collection create`, `collection rename` | Collection name; interchangeable with the name positional on both commands | -| `--search` | `collection list` | Only list collections matching this search term | -| `--file`, `--label` | `caption add` | Caption file to upload (`.vtt` or `.srt`), and the label shown in the player | -| `--force`, `-f` | `collection delete`, `caption delete` | Skip the prompts (required when there is no TTY to answer them) | - -Paid command flags: - -| Flag | Commands | Description | -| ----------------------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------- | -| `--jit`, `--codecs`, `--resolutions` | `encode enable` | Applied in the same request that switches the library to premium | -| `--languages`, `--source-language` | `transcribe` | Target languages (comma-separated) and the language spoken in the video | -| `--generate-title`, `--generate-description`, `--generate-chapters`, `--generate-moments` | `transcribe` | Also generate each field from the new transcript | -| `--title`, `--description`, `--chapters`, `--moments` | `smart` | What to generate; at least one is required | -| `--source-language` | `smart` | Caption track to read, e.g. `en`; auto-generated tracks are `-auto` (`en` falls back to `en-auto`) | -| `--transcribe` | `smart` | Transcribe a captionless video first without asking (paid); required to do so unattended | -| `--force`, `-f` | `transcribe` | The API's force flag: re-run and override the library's transcribing defaults | -| `--force`, `-f` | `smart` | Disable the library and video pickers, so nothing is guessed | +| Flag | Commands | Description | +| --------------- | ---------------------------------------------- | --------------------------------------------------------------- | +| `--name` | `collections update` | New collection name (`create` takes the name as its positional) | +| `--search` | `collections list` | Only list collections matching this search term | +| `--label` | `videos captions add` | Label shown in the player (defaults to the language code) | +| `--force`, `-f` | `collections delete`, `videos captions remove` | Skip the prompts (required when there is no TTY to answer them) | + +Paid video command flags (`bunny stream videos ...`): + +| Flag | Commands | Description | +| ----------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------ | +| `--target-languages`, `--source-language` | `transcribe` | Target languages (comma-separated) and the language spoken in the video | +| `--generate-title`, `--generate-description`, `--generate-chapters`, `--generate-moments` | `transcribe` | Also generate each field from the new transcript | +| `--title`, `--description`, `--chapters`, `--moments` | `smart` | What to generate; at least one is required | +| `--source-language` | `smart` | Caption track to read, e.g. `en`; auto-generated tracks are `-auto` (`en` falls back to `en-auto`) | +| `--transcribe` | `smart` | Transcribe a captionless video first without asking (paid); required to do so unattended | +| `--overwrite` | `transcribe` | Overwrite the existing transcription and override the library's transcribing defaults (the API's force flag) | +| `--force`, `-f` | `smart` | Disable the library and video pickers, so nothing is guessed | #### `bunny stream import` diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index c5555329..382e5833 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -37,6 +37,7 @@ const commands: CommandModule[] = [ sandboxNamespace, storageNamespace, sitesNamespace, + streamNamespace, configNamespace, skillsNamespace, docsCommand, @@ -48,7 +49,6 @@ const commands: CommandModule[] = [ const experimentalCommands: CommandModule[] = [ appsNamespace, registryNamespace, - streamNamespace, authNamespace, pzNamespace, ]; @@ -185,6 +185,7 @@ export const cli = instance ["Add a domain to manage DNS", "bunny dns zones create example.com"], ["Create a dev sandbox", "bunny sandbox create my-sandbox"], ["Deploy a static site", "bunny sites deploy"], + ["Upload a video to Stream", "bunny stream videos upload ./video.mp4"], // ["Deploy an app", "bunny apps deploy"], ]; diff --git a/packages/cli/src/commands/pz/create.ts b/packages/cli/src/commands/pz/create.ts index 7ddda887..fc166e5e 100644 --- a/packages/cli/src/commands/pz/create.ts +++ b/packages/cli/src/commands/pz/create.ts @@ -1,5 +1,6 @@ import { pullZonesCreate } from "@bunny.net/tools/pullzones"; import { defineToolCommand } from "@/core/define-tool-command.ts"; +import { examplesUrl } from "@/core/examples.ts"; import { logger } from "@/core/logger.ts"; import { saveManifest } from "@/core/manifest.ts"; import { confirm } from "@/core/ui.ts"; @@ -36,12 +37,15 @@ export const pzCreateCommand = defineToolCommand({ const link = await confirm(`Link this directory to "${zone.name}"?`, { optional: true, }); - if (!link) return; - saveManifest(PULL_ZONE_MANIFEST, { - id: zone.id, - name: zone.name, - }); - logger.success(`Linked to ${zone.name}.`); + if (link) { + saveManifest(PULL_ZONE_MANIFEST, { + id: zone.id, + name: zone.name, + }); + logger.success(`Linked to ${zone.name}.`); + } + logger.log(); + logger.dim(` Examples: ${examplesUrl("cdn")}`); }, render: () => {}, diff --git a/packages/cli/src/commands/sites/create.ts b/packages/cli/src/commands/sites/create.ts index 7a6a239d..8fdd517d 100644 --- a/packages/cli/src/commands/sites/create.ts +++ b/packages/cli/src/commands/sites/create.ts @@ -10,6 +10,7 @@ import { resolveConfig } from "@/config/index.ts"; import { clientOptions } from "@/core/client-options.ts"; import { defineCommand } from "@/core/define-command.ts"; import { errorMessage, UserError } from "@/core/errors.ts"; +import { examplesUrl } from "@/core/examples.ts"; import { formatKeyValue } from "@/core/format.ts"; import { normalizeHostname } from "@/core/hostnames/index.ts"; import { logger } from "@/core/logger.ts"; @@ -181,6 +182,7 @@ async function importExistingZone(opts: { logger.log(); logger.dim(" Go live on the existing hostnames: bunny sites deploy "); logger.dim(" Deploy from GitHub Actions: bunny sites ci init"); + logger.dim(` Examples: ${examplesUrl("sites")}`); } // Create a static site: a storage zone (files) and a pull zone (CDN) whose edge rules serve the published deploy dir; state lives at `_bunny/site.json` in the storage zone. @@ -421,5 +423,6 @@ export const sitesCreateCommand = defineCommand({ logger.log(); logger.dim(" Deploy your site: bunny sites deploy "); + logger.dim(` Examples: ${examplesUrl("sites")}`); }, }); diff --git a/packages/cli/src/commands/storage/zone/create.ts b/packages/cli/src/commands/storage/zone/create.ts index 1bc5e855..a5fb6242 100644 --- a/packages/cli/src/commands/storage/zone/create.ts +++ b/packages/cli/src/commands/storage/zone/create.ts @@ -38,6 +38,7 @@ import { resolveConfig } from "@/config/index.ts"; import { clientOptions } from "@/core/client-options.ts"; import { defineCommand } from "@/core/define-command.ts"; import { UserError } from "@/core/errors.ts"; +import { examplesUrl } from "@/core/examples.ts"; import { formatKeyValue } from "@/core/format.ts"; import { addHostname, @@ -588,6 +589,7 @@ export const storageZoneCreateCommand = defineCommand({ ` Upload files: bunny storage files upload -z ${zoneName}`, ); logger.dim(` Credentials: bunny storage zones credentials ${zoneName}`); + logger.dim(` Examples: ${examplesUrl("storage")}`); if (domainError) throw domainError; }, diff --git a/packages/cli/src/commands/stream/api.ts b/packages/cli/src/commands/stream/api.ts index 8641bcc2..24828c21 100644 --- a/packages/cli/src/commands/stream/api.ts +++ b/packages/cli/src/commands/stream/api.ts @@ -80,7 +80,7 @@ export async function resolveLibrary( if (!match?.Id) { throw new UserError( `No video library found for "${nameOrId}".`, - 'Run "bunny stream library list" to see your libraries.', + 'Run "bunny stream libraries list" to see your libraries.', ); } return fetchLibrary(client, match.Id); diff --git a/packages/cli/src/commands/stream/caption/delete.ts b/packages/cli/src/commands/stream/caption/delete.ts deleted file mode 100644 index 0ba65752..00000000 --- a/packages/cli/src/commands/stream/caption/delete.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { deleteVideoCaption } from "@/commands/stream/caption-api.ts"; -import { - resolveVideoInteractive, - streamLibraryContext, -} from "@/commands/stream/context.ts"; -import { defineCommand } from "@/core/define-command.ts"; -import { logger } from "@/core/logger.ts"; -import { confirm, requireConfirmable, withSpinner } from "@/core/ui.ts"; -import { captionLanguage } from "./add.ts"; - -interface CaptionDeleteArgs { - video: string; - lang: string; - lib?: string; - force?: boolean; -} - -export const streamCaptionDeleteCommand = defineCommand({ - command: "delete