Setting the file. One moment.
Lyria Recipe · Media Use · heygen-com/hyperframes · Skills Docs
ContentsBack to the top of the page ⋯
scripts/11 files
audio/scripts/lyria-recipe.py
audio/scripts/ lyria-recipe.py
Python · 136 lines · 5 KB
16 import argparse
17 import asyncio
18 import os
19 import sys
20 import wave
21 from pathlib import Path
22
23 # Windows sizes stdio to the ANSI code page (cp1252). These scripts emit UTF-8 on
24 # every platform; say so rather than depending on the console's code page. Carry
25 # `errors` across: reconfigure() resets it to "strict", and CPython deliberately gives
26 # stderr "backslashreplace" so the diagnostic path can never itself raise.
27 for _stream in (sys.stdout, sys.stderr):
28 if hasattr (_stream, "reconfigure" ):
29 _stream.reconfigure( encoding = "utf-8" , errors = _stream.errors)
30
31 DEFAULT_PROMPT = "Uplifting corporate tech, bright and modern, gentle piano with synth pads"
32 SAMPLE_RATE = 48000
33 CHANNELS = 2
34 SAMPLE_WIDTH = 2 # 16-bit
35
36
37 def parse_args () -> argparse.Namespace:
38 p = argparse.ArgumentParser( description = "Generate BGM via Google Lyria RealTime." )
39 p.add_argument( "--output" , required = True , help = "Output WAV path." )
40 p.add_argument( "--duration" , type = float , required = True , help = "Target duration in seconds." )
41 p.add_argument( "--prompt" , default = DEFAULT_PROMPT , help = "Mood / instrumentation prompt." )
42 p.add_argument( "--negative-prompt" , default = None , help = "Styles to exclude (optional)." )
43 p.add_argument( "--bpm" , type = int , default = 110 )
44 p.add_argument( "--brightness" , type = float , default = 0.8 , help = "0-1, higher = brighter mood." )
45 p.add_argument( "--density" , type = float , default = 0.5 , help = "0-1, higher = fuller mix." )
46 p.add_argument(
47 "--scale" ,
48 default = "MAJOR" ,
49 help = "MAJOR / MINOR / PENTATONIC / etc. — see google.genai.types.Scale. Pass empty string for none." ,
50 )
51 return p.parse_args()
52
53
54 async def generate_bgm (args: argparse.Namespace) -> dict :
55 from google import genai
56 from google.genai import types
57
58 api_key = os.environ.get( "GOOGLE_API_KEY" ) or os.environ.get( "GEMINI_API_KEY" ) or ""
59 if not api_key:
60 raise RuntimeError ( "Neither GOOGLE_API_KEY nor GEMINI_API_KEY is set." )
61
62 client = genai.Client(
63 api_key = api_key,
64 http_options = { "api_version" : "v1alpha" },
65 )
66
67 out_path = Path(args.output)
68 out_path.parent.mkdir( parents = True , exist_ok = True )
69
70 target_bytes = int (args.duration * SAMPLE_RATE * CHANNELS * SAMPLE_WIDTH )
71
72 cfg: dict = { "bpm" : args.bpm, "temperature" : 1.0 }
73 if args.density is not None :
74 cfg[ "density" ] = args.density
75 if args.brightness is not None :
76 cfg[ "brightness" ] = args.brightness
77 if args.scale:
78 scale_enum = getattr (types.Scale, args.scale, None )
79 if scale_enum:
80 cfg[ "scale" ] = scale_enum
81
82 prompts = [types.WeightedPrompt( text = args.prompt, weight = 1.0 )]
83 if args.negative_prompt:
84 prompts.append(types.WeightedPrompt( text = args.negative_prompt, weight =- 1.0 ))
85
86 buf = bytearray ()
87 timeout = args.duration + 8
88
89 async with client.aio.live.music.connect(
90 model = "models/lyria-realtime-exp" ,
91 ) as session:
92 await session.set_weighted_prompts( prompts = prompts)
93 await session.set_music_generation_config(
94 config = types.LiveMusicGenerationConfig( ** cfg),
95 )
96 await session.play()
97
98 async def collect ():
99 while len (buf) < target_bytes:
100 async for msg in session.receive():
101 sc = msg.server_content
102 if sc and sc.audio_chunks:
103 for chunk in sc.audio_chunks:
104 buf.extend(chunk.data)
105 if len (buf) >= target_bytes:
106 return
107 await asyncio.sleep( 1e-6 )
108
109 try :
110 await asyncio.wait_for(collect(), timeout = timeout)
111 except TimeoutError :
112 print ( f "Timeout after { timeout :.0f} s, collected { len (buf) } bytes" , file = sys.stderr)
113
114 audio = bytes (buf[:target_bytes])
115 with wave.open( str (out_path), "wb" ) as wf:
116 wf.setnchannels( CHANNELS )
117 wf.setsampwidth( SAMPLE_WIDTH )
118 wf.setframerate( SAMPLE_RATE )
119 wf.writeframes(audio)
120
121 actual_duration = len (audio) / ( SAMPLE_RATE * CHANNELS * SAMPLE_WIDTH )
122 print ( f "BGM: { out_path } ( { actual_duration :.2f} s)" )
123 return { "file" : str (out_path), "duration_sec" : round (actual_duration, 2 )}
124
125
126 def main () -> None :
127 args = parse_args()
128 try :
129 asyncio.run(generate_bgm(args))
130 except RuntimeError as exc:
131 print ( f "BGM generation failed: { exc } " , file = sys.stderr)
132 sys.exit( 1 )
133
134
135 if __name__ == "__main__" :
136 main()