No version is on PyPI yet. Clips Kitty bundles the SDK from the commit it is built from, so a plugin always runs with the SDK of the Clips Kitty it runs in.
The installed app puts its own SDK on the plugin's path, and it wins over one installed with pip. A plugin that uses a module added in a later SDK sets requires.clips_kitty to the first release that bundles it (Versioning).
media.jpegsays "there is no frame at … s in the video: is it that long?" for a time past the end with FFmpeg 7 and later too. Those end the read with an error ("received no packets") where older FFmpeg wrote nothing, so the plugin was told FFmpeg couldn't read the video.
- The edit step. A plugin with
editsin its outputs andmomentsin its inputs suggests edits for the clips Clips Kitty is about to make, in a run of its own (steps: ["edit"]). Clips Kitty keeps each suggestion for the creator, who can use it in the editor; it changes no clip by itself.contract.STEPSgainsedit,contract.PLANNED_STEPSis("export",),manifest.OUTPUTSgainsedits, andmanifest.PLANNED_OUTPUTSis("clips",).job.wants("edit")is True in an edit run. - The validator:
editsin outputs withoutmomentsin inputs is refused with "edits are suggested for the clips Clips Kitty makes from moments: add moments to inputs", and a plugin given moments may answer withedits("add ratings, context or edits to outputs"). A manifest still gets at most one of these errors.step_problemsays "can't suggest edits for clips: its manifest needs moments in inputs and edits in outputs", and points a plugin that only edits to Suggest edits rather than Rate & understand. job.suggest_edit(m)returns the clip'sEditSuggestion, the same one on every call, whose methods chain:cut,trim,keep_only,mute,volume,fade,speed,title_overlay(the editor's Hook title),cropandreason, withcuts,mutesandlengthto read back. Times are seconds of the video. A value outside the render's limits raisesContractErrorat the call; a span outside the clip, a layout the job's clips don't use, and a fade, speed or hook title length the editor doesn't offer are logged once. In a run not asked to edit, or on the plugin's own range, the call is logged and nothing is kept.job.limits.cropslists the layouts the job's clips can use (contract.CROPS:track,center,letterbox, or none).Moment.suggestedholds what edit plugins before this one suggested for the clip (Suggested); withouttranscript.readtheir reasons and hook titles are left out.check_resultchecks an answer'seditin an edit run: at most 20 cuts and 20 mutes, volume 0 to 2, fades 0 to 3 s, speed 0.5 to 3, a hook title of 1 to 120 characters for 1 to 10 s, a layout of at most 32 characters and a reason of at most 160. A problem refuses the whole answer; a field it doesn't name is ignored.host.read_edits()reads an edit run's answer as Clips Kitty keeps it: cuts and mutes clamped to the clip and joined, cuts that would leave less than the job's shortest clip ignored, fades, speed and the hook title's seconds set to the nearest of the editor's choices (contract.FADE_CHOICES,SPEED_CHOICES,HOOK_SECONDS_CHOICES) with achanged:line, the hook title on one line without web addresses, and a layout the job's clips don't use ignored. Every field it doesn't take gets anignored:line.read_answerssays "ignored: edits, because this run wasn't asked to suggest edits".host.clean_note(), and the reason and hook titleread_editskeeps, drop invisible format characters (a zero-width space, a word joiner, a right-to-left override) before taking out web addresses, so none can hide one. A zero-width joiner or non-joiner between two non-ASCII characters stays, for emoji and the scripts that need it.run --steps editruns an edit run, alone; with another step it is refused. A plugin that only suggests edits gets an edit run without--steps, and one that also finds, rates or understands is told it can be run again with--steps edit.--layout(standard, the default, orpodcast,sports,gaming,vertical-live,whole-frame) fillslimits.cropsas the app would.--momentsitems may carrysuggested. Each clip's line shows its length before and after and what is suggested.testing.run_plugin(..., steps=["edit"], layout="standard")returnsPluginRun.edits, each clip's suggestion as Clips Kitty keeps it;make_jobtakeslayouttoo.new --template editor, a sixth template: it suggests edits for the clips Clips Kitty makes, by the words said in them. It mutes the words the creator lists inmute_words, cuts the wait after "respawn timer" and adds the hook title "Quark burst!" where "quark burst" is said, for Quarkbloom Arena (a made-up game). Like the other templates it has its own tests and workflow, and it needs no FFmpeg.- Steps explains the edit step: what a plugin may suggest, how Clips Kitty fits it and what each
changed:andignored:line means, with an editor that the SDK's documentation tests run as written. A plugin that suggests edits needs 1.3.0: don't ship an older copy of the SDK inside a plugin, which comes first on the path and has nojob.suggest_edit.
python -m clipskitty_sdk --versionprints the SDK's version and the plugin contract it follows:clipskitty-sdk 1.2.0 (plugin contract 1).- Installed with pip, the SDK adds a
clipskitty-sdkcommand, the same aspython -m clipskitty_sdk. - A
testextra installs pytest, for a plugin's own tests:pip install "clipskitty-sdk[yaml,test]". - The package ships
py.typed, so type checkers read its type hints. - A wheel built from a folder where tests have run leaves out Python's byte-code caches (
*.pyc). - The package's details list the Python versions it supports, and link to its issues and this changelog.
editsis a planned output, for the edit step:outputs: [edits]is refused with "output 'edits' is planned, not supported by plugin API 1" instead of "unknown output".contract.PLANNED_STEPSnames the planned steps,editandexport.validateandrunshow the line ofclipskitty.yamleach problem is on, and "did you mean permissions?" under a misspelt field (Report.hints,manifest.line_marks). Without aclipskitty.yaml, or without PyYAML on the PC, they say so and exit with 2.version: 0.1is refused with "YAML read this as the number 0.1; write a version like 0.1.0", in the app too. Therun.python_requirementswarning says that pipelines run on Clips Kitty's own Python.validateandrunwarn about code that would fail on Clips Kitty's own Python (clipskitty_sdk.lint): syntax newer than Python 3.11, imports it doesn't promise (other packages, Clips Kitty's own code, modules the app leaves out or Windows lacks, a module at the plugin's root), and text files opened withoutencoding=. Files undertests/are skipped.host.APP_PYTHONis(3, 11), the Python Clips Kitty runs plugins on.host.plugin_envsetsPYTHONUTF8=0.runstops a plugin when Clips Kitty would (host.timeout_seconds:run.timeout_minutes, else 60 minutes to find and 10 to understand or rate) instead of after 600 seconds;--timeoutstill sets another limit.run: a refused run makes no job folder;--setvalues follow the setting's type, and an unknown one lists the plugin's settings;--stepstakes several words (--steps understand rate), and--steps editsays edit is planned;--game,--game-hintand--modelfillvideo.gamesandjob.models; a warning when the plugin asks for FFmpeg and none is found; each moment's reason (why:), and a warning for a label that isn't in the manifest'sevents; on a terminal, progress rewrites one line.run(main)started without a job folder (python src/main.py) says how to try the plugin and exits with 2.job.settings[name]for a setting with no value raisesSettingMissing: creators see a plain line and the log gets the developer's hint. For a secret the creator set, which is never injob.settings, the hint says to read it withjob.secret(name), and creators see the line for a mistake in the plugin's code. A slip in the plugin's own code (aKeyError,TypeErrorand the like) gives creators "it stopped on a mistake in its own code. Ask its developer to fix it.", with the exception and its line in the log; other exceptions keep their message.job.rateandjob.add_rangeshow the score they refused: "(got 140)".clipskitty_sdk.devrunholds the core ofrun, so other tools can run a plugin the same way.python -m clipskitty_sdk sample OUT.mp4writes a 40-second test video, made only with FFmpeg's own generators (a red banner at the top and a loud sound from 22 to 27 s, scene cuts at 10, 20 and 30 s), andOUT.transcript.json, which says "quark burst" from 21 to 26 s. It refuses to write inside a plugin's folder, which Clips Kitty copies on install, and its example of where to write instead is outside the plugin's folder, even when run fromsrc/. A path it can't write to is an error line, not a traceback, and leaves no half-written file.run --samplemakes that video and transcript in the job folder and runs the plugin on them. A plugin withoutvideo.readorffmpegneeds no FFmpeg for it: it gets the transcript and a 40-second length. A find run of a plugin withoutvideo.readno longer needs--video: its moments are fitted to--duration, else to the end of--transcript.python -m clipskitty_sdk frame VIDEO --at SECONDSwrites one frame as a PNG; with--region "0.30,0.10,0.40,0.10"it draws a box around that part of the screen and prints it in pixels.--regiontakes the numbers as one argument or several. It reads 10-bit (HDR) videos too. Likesample, it refuses to write inside a plugin's folder.clipskitty_sdk.samples:sample_transcript(),SAMPLE_VIDEO_SECONDS,make_sample(),parse_region(),region_pixels()andwrite_frame().clipskitty_sdk.testing, for a plugin's own tests:run_plugin()runs the plugin asrundoes and returns what it did (ok,error,moments,answers,notes,events,log);make_job()writes the job folder without running anything;sample_video()makes the sample video, and skips a pytest test when there is no FFmpeg;sample_transcript()andSAMPLE_VIDEO_SECONDSneed nothing. A run that can't start raisesContractError. Both takeollama_host,ollama_modelandmodels, asrun's--ollama-host,--ollama-modeland--modeldo, andrun_plugin()takessecrets, as--secretdoes.devrun.moments_from()anddevrun.transcript_from()take moments and a transcript as data rather than files.clipskitty_sdk.mediareads the video with the FFmpeg and FFprobe Clips Kitty hands over:Region(a part of the screen as fractions of the frame;Region.parse("0.30,0.10,0.40,0.10"),.pixels()),probe(),frames()(one region's frames, shrunk, at the rate asked),jpeg(),loudness()(per second) andscene_cuts(). Withoutffmpegorvideo.readin the permissions it says which to add (MediaError).loudness()andscene_cuts()are adapted from the MIT scene-cut example pipeline.clipskitty_sdk.signalsturns numbers and frames into moments:colour_share(),brightness(),difference(),spikes()(against a rolling median),stretches(),merge(), andaround(), which puts lead-in and tail around a moment and fits it to the job's limits and the video's length.clipskitty_sdk.text:words_of()reads a comma-separated words setting,find_words()matches whole words in any case or spacing,said()says when each word is said (by the words' own times when the transcript has them) andhits()which words are said during a moment.clipskitty_sdk.local_modelasks the creator's local model, on this PC only:model(),ask()(with pictures, and JSON with thinking off) andcan_see(). It refuses without theollamapermission, when no local model is set (Clips Kitty's AI runs at a cloud provider), and at an address that isn't 127.0.0.1, localhost or ::1; it never falls back to another address.runsays when a plugin with theollamapermission is given no--ollama-model.- Requests to this PC ignore proxy settings:
local_model's, andLocalAPI's when its address is 127.0.0.1, localhost or ::1. Before, a proxy set in the environment received them. They follow a redirect only to the same address (scheme, host and port).LocalAPIat any other address uses proxy settings as before. python -m clipskitty_sdk new FOLDER --template NAMEstarts a plugin from one of five templates:blank,transcript,game-events,raterandunderstander(new --listsays what each does).--publisher,--name,--game,--authorand--licensefill in the plugin's details; on a terminal it asks for a missing publisher and name, and elsewhere it usesyour-github-nameand the folder's name. It writes only into a new or empty folder, and checks theclipskitty.yamlit makes before writing anything. Each plugin it makes hassrc/main.py,README.md,tests/test_main.py(usingclipskitty_sdk.testing),CHANGELOG.md,.gitignore,LICENSE(MIT; another--licenseleaves it out and says to add one),TEMPLATE-LICENSE.txt(the SDK's MIT notice, for the code that came from the template) and a GitHub workflow that validates the plugin and runs its tests on Clips Kitty's Python (3.11), on Linux and Windows. The templates ship inside the package (clipskitty_sdk.scaffold). After making a plugin,newlinks Your first game pipeline.run(main): importing a part of the SDK that the SDK running the plugin doesn't have (such asfrom clipskitty_sdk import mediaon an older one) gives "This pipeline needs a newer version of Clips Kitty. Update Clips Kitty, or ask the pipeline's developer which version it needs." (job.NEWER_VERSION,job.needs_newer_sdk()), with the import error in the log. Clips Kitty says the same when such an import is at the top of the plugin's file, instead of "needs clipskitty_sdk".python -m clipskitty_sdk install FOLDERputs a plugin into the Clips Kitty running on this PC, as Marketplace › Browse › For developers does: it prints Clips Kitty's own install screen as text and asks first (clipskitty_sdk.installer).--yesskips the question only when nothing is new (the same plugin is installed and the update adds no permission, network host, data sent, change to where it runs, or step), and--watchreinstalls it on each save until a save adds something. It refuses.venv,venvandnode_modulesinside the folder and symbolic links, says when Clips Kitty isn't running or came out before plugin support, reads the session secret from--data-dir, the file Clips Kitty names or the installed app's data folder, and talks only to Clips Kitty on this PC, ignoring proxy settings. A mistyped--api, an answer that isn't HTTP and one cut off part way are error lines, not tracebacks.LocalAPInever sends the session secret.python -m clipskitty_sdk listing FOLDER --section SECTIONwrites the file that lists a finished plugin in Awesome Clips Kitty, the catalog the Marketplace reads (clipskitty_sdk.listing): its id, repository, path, section,--aliaswords, the date and this version at its full commit, with its tag whenv<version>points there.--toadds the version to a listing that exists and refuses one already listed. It refuses a folder with changes that aren't committed, a commit not on a pushed branch, a repository other than the manifest's, an id whose publisher doesn't own the repository, and a template's placeholders. It runs only git commands that read and fetches nothing, then says to open the pull request with the catalog's index rebuilt, which CI checks. When git can't read the repository (one owned by another user, say), it shows git's own words and the command git suggests.manifest.yaml_data()reads a YAML text with PyYAML's safe loader, for the SDK's other YAML files.
- Plugins can understand and rate moments that were found before them, as well as find their own (Steps).
job.moments: the moments handed over to understand or rate, asMoments.job.understand(moment, text)andjob.rate(moment, score, reason): a run's answers.job.wants(step): whether this run is asked tofind,understandorrate.job.text(moment): what is said during a moment.Moment, exported fromclipskitty_sdk.run --stepsand--momentstry a moment run;--durationand--min-scorego with them, and--videois needed only to find moments or for a plugin withvideo.read.
- The job contract:
job.jsonin, progress lines out,result.jsonback (clipskitty_sdk.contract,check_job,check_result). read_job(),run(main)andJob:progress,log,secret,add_range,finishandfail.- The manifest checks the app, the plugin manager and the registry run (
clipskitty_sdk.manifest). clipskitty_sdk.host: the job folder, the process and the result, as the app runs a plugin.- The
validate,schemaandruncommands. LocalAPI: a small client for Clips Kitty's local API.