GEMINI LABJP
VIDEO — Agentic video understanding reached 3.7 Flash, 3.6 Flash, and 3.5 Flash-Lite on September 1. The model navigates the timeline itself rather than sampling frames at a fixed rateTOKENS — Because it pulls transcripts, frames, or audio only when it needs them, Google measures up to 88% fewer tokens on long-form contentSCOPE — It works across both the Interactions and GenerateContent APIs. If you have costed out long-video work before, the assumptions have movedMUSIC — Lyria 3.5 entered public preview on September 3, generating full-length songs at 44.1 kHz stereoCONTROL — Lyria 3.5 accepts text and image inputs, with better musical coherence, more natural vocals, and finer control over duration and structureROBOTICS — gemini-robotics-er-2-streaming-preview is tuned for real-time streaming over the Live API, with function calling that blocks on physical robot actionsVIDEO — Agentic video understanding reached 3.7 Flash, 3.6 Flash, and 3.5 Flash-Lite on September 1. The model navigates the timeline itself rather than sampling frames at a fixed rateTOKENS — Because it pulls transcripts, frames, or audio only when it needs them, Google measures up to 88% fewer tokens on long-form contentSCOPE — It works across both the Interactions and GenerateContent APIs. If you have costed out long-video work before, the assumptions have movedMUSIC — Lyria 3.5 entered public preview on September 3, generating full-length songs at 44.1 kHz stereoCONTROL — Lyria 3.5 accepts text and image inputs, with better musical coherence, more natural vocals, and finer control over duration and structureROBOTICS — gemini-robotics-er-2-streaming-preview is tuned for real-time streaming over the Live API, with function calling that blocks on physical robot actions
Articles/API / SDK
API / SDK/2026-09-07Intermediate

The day Lyria 3.5 landed, I changed how my audio folders are laid out

When Lyria 3.5 brought full-length generation, my generated takes were sitting in the same folder as the tracks I had chosen by hand. Here is the forty-line ledger gate that draws the line by hash, not by filename.

Lyria 3.5music generation2Gemini API234Python45indie development22release management

I read that Lyria 3.5 had moved into public preview on September 3rd while I was in the middle of reshuffling candidate tracks for the next update of my healing-sound app. Full-length music generation, finer control over length and structure, 44.1kHz stereo output — I stopped reading there.

What stopped me was closer to unease than excitement. Looking at the folder in front of me, the tracks I had chosen by hand and the takes I had generated for comparison were sitting at the same level. I had prefixed the generated ones with draft_, but that prefix was the whole of my boundary.

Let me say up front that this is not a piece about how well the model performs. The better generation gets, the more the decision about what belongs in the shipped product has to be protected by a mechanism rather than by mood. I ended up putting that mechanism into a forty-odd-line script.

A filename prefix disappears on the first copy

For a while I assumed the prefix was enough. Generated takes got draft_, hand-picked tracks kept their names, and I could tell them apart by eye.

That did not hold up. Run the files through an export tool, move them to another machine, renumber a batch — the prefix comes off each time, and the only moment I could notice was the next time I happened to open that file.

Sure enough, a file called calm_13_new.wav was still sitting in the candidate folder. Looking back, it was something I had set aside meaning to decide later, and then forgot. From the name alone I could not tell whether I had chosen it myself or dropped it in as a generated comparison.

Filenames, locations, and modification times all get rewritten by a single human action. Unless the primary key is something that does not get rewritten, the boundary will not hold.

What Lyria 3.5 changed (public preview, September 3, 2026)

Here are the facts first.

ItemDetail
AvailabilityPublic preview (September 3, 2026)
LengthFull-length music generation
ImprovementsMusical coherence, natural vocals, finer control over length and structure
InputText and images
OutputHigh-fidelity 44.1kHz stereo audio

The line that mattered to me was not about fidelity. It was that a whole piece now arrives in one go. A 30-second clip announces itself as a sketch the moment you hear it. Several minutes of finished-sounding music sits in the folder looking exactly like a candidate.

You cannot keep sorting things by eye once they stop looking different. That is the point where I decided to change how the folders were laid out.

Only the hash of the contents survives a copy

So I moved the primary key of my ledger from the filename to the SHA-256 of the contents. Rename the file, move the folder, touch the timestamp — as long as the bytes are the same, the hash is the same. And if I re-export with different settings, the hash changes too, which means "I rebuilt it and forgot to register it" gets caught by the same mechanism.

The ledger is one line per track, tab-separated, with three columns.

ColumnValueMeaning
1SHA-256Fingerprint of the contents. This is the key
2originhand (chosen by me) / generated / reference
3noteA human-readable memo, usually the original filename

And only hand is allowed in the ship folder.

Tracks I chose by hand go out; generated takes stay on the scouting shelf. Writing that single rule into a constant in the script, rather than keeping it in my head, is what made the hesitation go away.

The forty-odd-line ledger gate

Here is the script as it stands. Standard library only.

#!/usr/bin/env python3
"""Gate that lets only approved audio into the ship folder."""
import hashlib
import pathlib
import sys
 
AUDIO_SUFFIXES = {".wav", ".mp3", ".m4a", ".aac", ".flac", ".ogg", ".caf"}
SHIPPABLE = {"hand"}
 
 
def digest(path: pathlib.Path) -> str:
    h = hashlib.sha256()
    with path.open("rb") as f:
        for chunk in iter(lambda: f.read(1 << 20), b""):
            h.update(chunk)
    return h.hexdigest()
 
 
def load_ledger(path: pathlib.Path) -> dict:
    ledger = {}
    for lineno, raw in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
        line = raw.strip()
        if not line or line.startswith("#"):
            continue
        parts = line.split("\t")
        if len(parts) < 3:
            sys.exit(f"ledger line {lineno} has too few columns: {raw!r}")
        ledger[parts[0]] = (parts[1], parts[2])
    return ledger
 
 
def main(ship_dir: str, ledger_file: str) -> int:
    ledger = load_ledger(pathlib.Path(ledger_file))
    unregistered, blocked, passed = [], [], 0
    for path in sorted(pathlib.Path(ship_dir).rglob("*")):
        if not path.is_file() or path.suffix.lower() not in AUDIO_SUFFIXES:
            continue
        entry = ledger.get(digest(path))
        if entry is None:
            unregistered.append(path)
        elif entry[0] not in SHIPPABLE:
            blocked.append((path, entry[0]))
        else:
            passed += 1
 
    for path in unregistered:
        print(f"unregistered: {path}")
    for path, origin in blocked:
        print(f"not shippable ({origin}): {path}")
    print(f"passed {passed} / unregistered {len(unregistered)} / blocked {len(blocked)}")
    return 1 if unregistered or blocked else 0
 
 
if __name__ == "__main__":
    if len(sys.argv) != 3:
        sys.exit("usage: audit.py <ship_dir> <ledger.tsv>")
    sys.exit(main(sys.argv[1], sys.argv[2]))

Three notes on why it is written this way.

First, SHIPPABLE is a set. Right now it holds only hand, but a day will come when I add purchased material as licensed. When that happens I would rather add one word to a set than rewrite a condition. Keeping the criterion in one place is a kindness to whoever reads this next, which is usually me.

Second, unregistered and blocked are counted separately. Both stop the build, but they call for different actions. Unregistered means a step was skipped — check it, register it, move on. Blocked means a judgment went wrong, and nothing should proceed until that file leaves the ship folder. Collapsing them into a single count throws that distinction away.

Third, the file is read in 1 MiB chunks. Full-length pieces get large, and I would rather not load an entire library into memory to fingerprint it.

The numbers I got on my own machine

I first ran it against a layout close to the candidate folder I described at the start: 19 audio files, 17 of them registered.

unregistered: ship/bgm/calm_13_new.wav
not shippable (generated): ship/bgm/draft_lyria_take3.wav
passed 17 / unregistered 1 / blocked 1

Exit code 1. The two files I could no longer tell apart by name were stopped for two different reasons. Those two lines were exactly what I wanted.

I timed it as well. Against 40 tracks of 30-second 44.1kHz stereo audio, 203 MB in total, it took 0.50 seconds on all three runs. Hashing reads every byte, so the time scales with total size rather than file count. Even for a library of a few gigabytes, running it once right before an export is not a wait worth optimizing away.

I put it immediately before the shipping export starts. Noticing afterwards means rebuilding the artifact.

Inspecting the audio itself is a separate stage. For things that can be settled numerically without listening — loop seams, for instance — I wrote that up in Screen Loop Seam Clicks With Numbers Before You Hand the Audio to Gemini. Keeping the "where did this come from" check apart from the "is this good" check keeps both of them short.

Not shutting generation out, but giving it a destination

One thing I would rather not be misread on: I have not decided against using generated audio. I have only separated the inside of the shipped product from the outside.

Generated takes now live in three places. One is screen prototypes, where they stand in as placeholder audio while I settle length and structure. Another is as a rough draft when I am assembling something myself, to see one possible shape of the arrangement. The third is as a comparison point when I want to check whether an existing track has gone flat. None of those reach a listener's device.

The models on the Gemini side have shifted generations several times in the past few weeks alone. On how a bill can grow even when the per-token price stays put, I wrote Gemini 3.8 Flash Costs the Same Per Token and Can Still Raise Your Bill. Fixing the boundary in place is what keeps me from redoing the judgment every time a model changes underneath it.

If you try one thing today, scanning your ship folder once and re-registering it by hash instead of by filename is enough. That is where I started too.

Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API / SDK2026-03-28
Lyria 3 Pro API Complete Implementation Guide — Generate Professional Full-Length Tracks from Text and Images
Learn how to generate full-length music tracks using Google DeepMind's Lyria 3 Pro. Covers Clip/Pro/RealTime model differences, Interactions API, prompt engineering, and monetization strategies.
API / SDK2026-08-27
Your Spreadsheet Breaks Before Gemini Ever Sees It
Merged cells and two-row headers quietly strip rows of their keys during extraction, long before the model reads anything. Here is what gets lost, measured, plus the Python that flattens the table and catches the total row.
API / SDK2026-08-27
Record what you send before you try to measure whether temperature still works
Deprecated sampling parameters still return 200 and are silently ignored. Here is how a runtime recorder caught the call sites grep and AST both missed, kept the construction site attached to each config, and turned the ledger into a CI gate.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links