{"TotalCount":7,"Files":[{"Ident":"carsonk.tiktoktts","Path":"Code/TikTokTTS.cs","FileName":"TikTokTTS.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":55749,"IsPrivate":false,"Code":"using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Threading.Tasks;\r\nusing Sandbox;\r\n\r\nnamespace TikTokTTS;\r\n\r\n/// \u003Csummary\u003E\r\n/// A static class that allows you to use the TikTokTTS API.\r\n/// \u003C/summary\u003E\r\npublic static class TikTokTTS\r\n{\r\n    /// \u003Csummary\u003E\r\n    /// The API endpoint that the library uses.\r\n    /// \u003C/summary\u003E\r\n    public static string ENDPOINT = \u0022https://tiktok-tts.weilnet.workers.dev\u0022;\r\n\r\n    static int currentId = 0;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Use TikTok\u0027s TTS to speak via a MusicPlayer. This MusicPlayer will automatically dispose itself when it finishes playing.\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022text\u0022\u003EThe text to be said by the TTS\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022voice\u0022\u003EThe name of the voice\u003C/param\u003E\r\n    /// \u003Creturns\u003E\u003C/returns\u003E\r\n    public static async Task\u003CMusicPlayer\u003E Say( string text, string voice = \u0022en_us_007\u0022 )\r\n    {\r\n        string fileName = $\u0022tts-{currentId}.mp3\u0022;\r\n        currentId\u002B\u002B;\r\n\r\n        if ( !FileSystem.Data.FileExists( fileName ) )\r\n        {\r\n            await Download( text, voice, fileName, FileSystem.Data );\r\n        }\r\n\r\n        var musicPlayer = MusicPlayer.Play( FileSystem.Data, fileName );\r\n        musicPlayer.ListenLocal = true;\r\n\r\n        return musicPlayer;\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Use TikTok\u0027s TTS to speak and download/save the mp3 file.\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022text\u0022\u003EThe text to be said by the TTS\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022voice\u0022\u003EThe name of the voice\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022fileName\u0022\u003EThe file name/path it should download to (defaults to \u0022tts-##########.mp3\u0022\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022fileSystem\u0022\u003EThe filesystem to use (defaults to FileSystem.Data)\u003C/param\u003E\r\n    /// \u003Creturns\u003EThe file path\u003C/returns\u003E\r\n    public static async Task\u003Cstring\u003E Download( string text, string voice = \u0022en_us_007\u0022, string fileName = null, BaseFileSystem fileSystem = null )\r\n    {\r\n        if ( fileSystem == null ) fileSystem = FileSystem.Data;\r\n        if ( string.IsNullOrEmpty( fileName ) ) fileName = $\u0022tts-{new Guid()}.mp3\u0022;\r\n\r\n        var content = new Dictionary\u003Cstring, string\u003E {\r\n            { \u0022text\u0022, text },\r\n            { \u0022voice\u0022, voice }\r\n        };\r\n        var headers = new Dictionary\u003Cstring, string\u003E {\r\n            { \u0022Content-Type\u0022, \u0022application/json\u0022 }\r\n        };\r\n\r\n        text = text.RemoveBadCharacters();\r\n        text = text.Substring( 0, Math.Min( text.Length, 300 ) );\r\n        if ( !text.Any( x =\u003E char.IsLetterOrDigit( x ) ) ) return null;\r\n        if ( string.IsNullOrEmpty( text ) ) return null;\r\n\r\n        // Make sure folders exist\r\n        var folders = fileName.Split( \u0027/\u0027 );\r\n        if ( folders.Length == 1 ) folders = fileName.Split( \u0027\\\\\u0027 );\r\n        if ( folders.Length \u003E 1 )\r\n        {\r\n            string path = \u0022\u0022;\r\n            for ( int i = 0; i \u003C folders.Length - 1; i\u002B\u002B )\r\n            {\r\n                path \u002B= folders[i] \u002B \u0022/\u0022;\r\n                if ( !fileSystem.DirectoryExists( path ) )\r\n                    fileSystem.CreateDirectory( path );\r\n            }\r\n        }\r\n\r\n        try\r\n        {\r\n            if ( fileSystem.FileExists( fileName ) )\r\n                fileSystem.DeleteFile( fileName );\r\n            var response = await Http.RequestJsonAsync\u003CTikTokTTSResponse\u003E( \u0022https://tiktok-tts.weilnet.workers.dev/api/generation\u0022, \u0022POST\u0022, Http.CreateJsonContent( content ), headers );\r\n            string base64 = response.data;\r\n            byte[] mp3 = Convert.FromBase64String( base64 );\r\n            var stream = fileSystem.OpenWrite( fileName );\r\n            stream.Write( mp3, 0, mp3.Length );\r\n            stream.Close();\r\n            return fileName;\r\n        }\r\n        catch ( Exception e )\r\n        {\r\n            Log.Error( e.Message );\r\n            return null;\r\n        }\r\n    }\r\n\r\n    class TikTokTTSResponse\r\n    {\r\n        public bool success { get; set; }\r\n        public string data { get; set; }\r\n        public string error { get; set; }\r\n    }\r\n}"},{"Ident":"carsonk.tiktoktts","Path":"UnitTests/LibraryTest.cs","FileName":"LibraryTest.cs","PackageType":"library","CodeKind":"UnitTest","AssetVersionId":55749,"IsPrivate":false,"Code":"using Sandbox;\r\n\r\npublic partial class LibraryTests\r\n{\r\n\t[TestMethod]\r\n\tpublic void SceneTest()\r\n\t{\r\n\t\tvar scene = new Scene();\r\n\t\tusing ( scene.Push() )\r\n\t\t{\r\n\t\t\tvar go = new GameObject();\r\n\r\n\t\t\tAssert.AreEqual( 1, scene.Directory.GameObjectCount );\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"},{"Ident":"carsonk.tiktoktts","Path":"Editor/TikTokSpeechControlWidget.cs","FileName":"TikTokSpeechControlWidget.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":55749,"IsPrivate":false,"Code":"using Editor;\r\nusing Sandbox;\r\nusing System.Linq;\r\n\r\nnamespace TikTokTTS.Editor;\r\n\r\n[CustomEditor( typeof( TikTokSpeech ) )]\r\npublic class TikTokSpeechComponentEditor : ComponentEditorWidget\r\n{\r\n\r\n\tSerializedObject Target;\r\n\r\n\tpublic TikTokSpeechComponentEditor( SerializedObject obj ) : base( obj )\r\n\t{\r\n\t\tTarget = obj;\r\n\t\tLayout = Layout.Column();\r\n\t\tvar sheet = new PropertyControlSheet();\r\n\t\tsheet.AddObject( obj );\r\n\t\tLayout.Add( sheet );\r\n\t\tLayout.Spacing = 2;\r\n\t\tMinimumHeight = 70;\r\n\r\n\t\tvar buttonPanel = Layout.Column();\r\n\t\tbuttonPanel.Margin = new Sandbox.UI.Margin( 12, 0 );\r\n\t\tvar button = new Button( \u0022Speak\u0022, \u0022play_arrow\u0022 )\r\n\t\t{\r\n\t\t\tWidth = 200,\r\n\t\t\tClicked = () =\u003E\r\n\t\t\t{\r\n\t\t\t\tvar component = Target.Targets.First() as TikTokSpeech;\r\n\t\t\t\tcomponent?.Speak();\r\n\t\t\t}\r\n\t\t};\r\n\t\tbuttonPanel.Add( button );\r\n\t\tLayout.Add( buttonPanel );\r\n\t}\r\n\r\n\tprotected override void OnPaint()\r\n\t{\r\n\t}\r\n\r\n}"},{"Ident":"carsonk.tiktoktts","Path":"Code/TikTokVoices.cs","FileName":"TikTokVoices.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":55749,"IsPrivate":false,"Code":"using Sandbox;\r\n\r\nnamespace TikTokTTS;\r\n\r\npublic enum TikTokVoice\r\n{\r\n    [Icon( \u0022\uD83C\uDDFA\uD83C\uDDF8\u0022 ), Title( \u0022US Female - Jessie (en_us_002)\u0022 )] en_us_002,\r\n    [Icon( \u0022\uD83C\uDDFA\uD83C\uDDF8\u0022 ), Title( \u0022US Female - Empathetic (en_female_samc)\u0022 )] en_female_samc,\r\n    [Icon( \u0022\uD83C\uDDFA\uD83C\uDDF8\u0022 ), Title( \u0022US Female - Beauty Guru (en_female_makeup)\u0022 )] en_female_makeup,\r\n    [Icon( \u0022\uD83C\uDDFA\uD83C\uDDF8\u0022 ), Title( \u0022US Female - Bestie (en_female_richgirl)\u0022 )] en_female_richgirl,\r\n\r\n    [Icon( \u0022\uD83C\uDDFA\uD83C\uDDF8\u0022 ), Title( \u0022US Male - Joey (en_us_006)\u0022 )] en_us_006,\r\n    [Icon( \u0022\uD83C\uDDFA\uD83C\uDDF8\u0022 ), Title( \u0022US Male - Professor (en_us_007)\u0022 )] en_us_007,\r\n    [Icon( \u0022\uD83C\uDDFA\uD83C\uDDF8\u0022 ), Title( \u0022US Male - Scientist (en_us_009)\u0022 )] en_us_009,\r\n    [Icon( \u0022\uD83C\uDDFA\uD83C\uDDF8\u0022 ), Title( \u0022US Male - Confidence (en_us_010)\u0022 )] en_us_010,\r\n    [Icon( \u0022\uD83C\uDDFA\uD83C\uDDF8\u0022 ), Title( \u0022US Male - Serious (en_male_cody)\u0022 )] en_male_cody,\r\n    [Icon( \u0022\uD83C\uDDFA\uD83C\uDDF8\u0022 ), Title( \u0022US Male - Wacky (en_male_funny)\u0022 )] en_male_funny,\r\n    [Icon( \u0022\uD83C\uDDFA\uD83C\uDDF8\u0022 ), Title( \u0022US Male - Story Teller (en_male_narration)\u0022 )] en_male_narration,\r\n    [Icon( \u0022\uD83C\uDDFA\uD83C\uDDF8\u0022 ), Title( \u0022US Male - Game On (en_male_jomboy)\u0022 )] en_male_jomboy,\r\n    [Icon( \u0022\uD83C\uDDFA\uD83C\uDDF8\u0022 ), Title( \u0022US Male - Alfred (en_male_jarvis)\u0022 )] en_male_jarvis,\r\n\r\n    [Icon( \u0022\uD83C\uDDEC\uD83C\uDDE7\u0022 ), Title( \u0022UK Male - Narrator (en_uk_001)\u0022 )] en_uk_001,\r\n    [Icon( \u0022\uD83C\uDDEC\uD83C\uDDE7\u0022 ), Title( \u0022UK Male - English (en_uk_003)\u0022 )] en_uk_003,\r\n    [Icon( \u0022\uD83C\uDDEC\uD83C\uDDE7\u0022 ), Title( \u0022UK Male - Olantekkers (en_male_olantekkers)\u0022 )] en_male_olantekkers,\r\n\r\n    [Icon( \u0022\uD83C\uDDE6\uD83C\uDDFA\u0022 ), Title( \u0022AU Female - Metro (en_au_001)\u0022 )] en_au_001,\r\n    [Icon( \u0022\uD83C\uDDE6\uD83C\uDDFA\u0022 ), Title( \u0022AU Female - Peaceful (en_female_emotional)\u0022 )] en_female_emotional,\r\n    [Icon( \u0022\uD83C\uDDE6\uD83C\uDDFA\u0022 ), Title( \u0022AU Male - Smooth (en_au_002)\u0022 )] en_au_002,\r\n    [Icon( \u0022\uD83C\uDDE6\uD83C\uDDFA\u0022 ), Title( \u0022AU Male - Ashmagic (en_male_ashmagic)\u0022 )] en_male_ashmagic,\r\n\r\n    [Icon( \u0022\uD83D\uDC98\u0022 ), Title( \u0022Festive - Cupid (en_male_cupid)\u0022 )] en_male_cupid,\r\n    [Icon( \u0022\uD83C\uDF85\u0022 ), Title( \u0022Festive - Narration (en_male_santa_narration)\u0022 )] en_male_santa_narration,\r\n    [Icon( \u0022\uD83C\uDF85\u0022 ), Title( \u0022Festive - Santa (en_male_santa_effect)\u0022 )] en_male_santa_effect,\r\n\r\n    [Icon( \u0022\uD83D\uDE31\u0022 ), Title( \u0022Disney - Scream (en_us_ghostface)\u0022 )] en_us_ghostface,\r\n    [Icon( \u0022\uD83D\uDC36\u0022 ), Title( \u0022Disney - Chewbacca (en_us_chewbacca)\u0022 )] en_us_chewbacca,\r\n    [Icon( \u0022\uD83E\uDD16\u0022 ), Title( \u0022Disney - C3PO (en_us_c3po)\u0022 )] en_us_c3po,\r\n    [Icon( \u0022\uD83E\uDD76\u0022 ), Title( \u0022Disney - Stitch (en_us_stitch)\u0022 )] en_us_stitch,\r\n    [Icon( \u0022\uD83E\uDE96\u0022 ), Title( \u0022Disney - Stormtrooper (en_us_stormtrooper)\u0022 )] en_us_stormtrooper,\r\n    [Icon( \u0022\uD83D\uDE80\u0022 ), Title( \u0022Disney - Rocket (en_us_rocket)\u0022 )] en_us_rocket,\r\n    [Icon( \u0022\uD83D\uDC69\u200D\uD83E\uDDB3\u0022 ), Title( \u0022Disney - Madame Leota (en_female_madam_leota)\u0022 )] en_female_madam_leota,\r\n    [Icon( \u0022\uD83D\uDC7B\u0022 ), Title( \u0022Disney - Ghost Host (en_male_ghosthost)\u0022 )] en_male_ghosthost,\r\n    [Icon( \u0022\u2620\uFE0F\u0022 ), Title( \u0022Disney - Pirate (en_male_pirate)\u0022 )] en_male_pirate,\r\n\r\n    [Icon( \u0022\uD83E\uDDFD\u0022 ), Title( \u0022Silly - Trickster/Spongebob (en_male_grinch)\u0022 )] en_male_grinch,\r\n    [Icon( \u0022\uD83E\uDDD4\u0022 ), Title( \u0022Silly - Mr. GoodGuy (en_male_deadpool)\u0022 )] en_male_deadpool,\r\n    [Icon( \u0022\uD83D\uDE4D\u0022 ), Title( \u0022Silly - Lord Cringe (en_male_ukneighbor)\u0022 )] en_male_ukneighbor,\r\n    [Icon( \u0022\uD83E\uDD35\u0022 ), Title( \u0022Silly - Mr. Meticulous (en_male_ukbutler)\u0022 )] en_male_ukbutler,\r\n    [Icon( \u0022\uD83E\uDD42\u0022 ), Title( \u0022Silly - Debutante (en_female_shenna)\u0022 )] en_female_shenna,\r\n    [Icon( \u0022\uD83E\uDD2A\u0022 ), Title( \u0022Silly - Varsity (en_female_pansino)\u0022 )] en_female_pansino,\r\n    [Icon( \u0022\uD83D\uDC68\u200D\uD83D\uDD2C\u0022 ), Title( \u0022Silly - Marty (en_male_trevor)\u0022 )] en_male_trevor,\r\n    [Icon( \u0022\uD83D\uDE45\u0022 ), Title( \u0022Silly - Bae (en_female_betty)\u0022 )] en_female_betty,\r\n    [Icon( \u0022\uD83D\uDC75\u0022 ), Title( \u0022Silly - Granny (en_female_grandma)\u0022 )] en_female_grandma,\r\n    [Icon( \u0022\uD83E\uDE84\u0022 ), Title( \u0022Silly - Magician (en_male_wizard)\u0022 )] en_male_wizard,\r\n\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Music - Alto/Cottagecore (en_female_f08_salut_damour)\u0022 )] en_female_f08_salut_damour,\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Music - Tenor/Jingle (en_male_m03_lobby)\u0022 )] en_male_m03_lobby,\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Music - Sunshine Soon/Toon Beat (en_male_m03_sunshine_soon)\u0022 )] en_male_m03_sunshine_soon,\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Music - Open Mic (en_female_f08_warmy_breeze)\u0022 )] en_female_f08_warmy_breeze,\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Music - Euphoric (en_female_ht_f08_glorious)\u0022 )] en_female_ht_f08_glorious,\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Music - It Goes Up/Hypetrain (en_male_sing_funny_it_goes_up)\u0022 )] en_male_sing_funny_it_goes_up,\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Music - Chipmunk/Quirky Time (en_male_m2_xhxs_m03_silly)\u0022 )] en_male_m2_xhxs_m03_silly,\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Music - Melodrama (en_female_ht_f08_wonderful_world)\u0022 )] en_female_ht_f08_wonderful_world,\r\n\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Parody - Pop Lullaby (en_female_f08_twinkle)\u0022 )] en_female_f08_twinkle,\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Parody - Classic Electric (en_male_m03_classical)\u0022 )] en_male_m03_classical,\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Parody - Cozy (en_male_m2_xhxs_m03_christmas)\u0022 )] en_male_m2_xhxs_m03_christmas,\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Parody - Caroler (en_male_sing_deep_jingle)\u0022 )] en_male_sing_deep_jingle,\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Parody - NYE 2023 (en_female_ht_f08_newyear)\u0022 )] en_female_ht_f08_newyear,\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Parody - Opera (en_female_ht_f08_halloween)\u0022 )] en_female_ht_f08_halloween,\r\n    [Icon( \u0022\uD83C\uDFB6\u0022 ), Title( \u0022Parody - Thanksgiving (en_male_sing_funny_thanksgiving)\u0022 )] en_male_sing_funny_thanksgiving,\r\n\r\n    [Icon( \u0022\uD83C\uDDEB\uD83C\uDDF7\u0022 ), Title( \u0022French - Male 1 (fr_001)\u0022 )] fr_001,\r\n    [Icon( \u0022\uD83C\uDDEB\uD83C\uDDF7\u0022 ), Title( \u0022French - Male 2 (fr_002)\u0022 )] fr_002,\r\n\r\n    [Icon( \u0022\uD83C\uDDEA\uD83C\uDDF8\u0022 ), Title( \u0022Spanish - Spain Male (es_002)\u0022 )] es_002,\r\n\r\n    [Icon( \u0022\uD83C\uDDF2\uD83C\uDDFD\u0022 ), Title( \u0022Spanish - MX Male (es_mx_002)\u0022 )] es_mx_002,\r\n\r\n    [Icon( \u0022\uD83C\uDDE7\uD83C\uDDF7\u0022 ), Title( \u0022Portuguese BR - Female 1 (br_001)\u0022 )] br_001,\r\n    [Icon( \u0022\uD83C\uDDE7\uD83C\uDDF7\u0022 ), Title( \u0022Portuguese BR - Female 2 (br_003)\u0022 )] br_003,\r\n    [Icon( \u0022\uD83C\uDDE7\uD83C\uDDF7\u0022 ), Title( \u0022Portuguese BR - Female 3 (br_004)\u0022 )] br_004,\r\n    [Icon( \u0022\uD83C\uDDE7\uD83C\uDDF7\u0022 ), Title( \u0022Portuguese BR - Male (br_005)\u0022 )] br_005,\r\n\r\n    [Icon( \u0022\uD83C\uDDE9\uD83C\uDDEA\u0022 ), Title( \u0022German - Female (de_001)\u0022 )] de_001,\r\n    [Icon( \u0022\uD83C\uDDE9\uD83C\uDDEA\u0022 ), Title( \u0022German - Male (de_002)\u0022 )] de_002,\r\n\r\n    [Icon( \u0022\uD83C\uDDEE\uD83C\uDDE9\u0022 ), Title( \u0022Indonesian - Female (id_001)\u0022 )] id_001,\r\n\r\n    [Icon( \u0022\uD83C\uDDEF\uD83C\uDDF5\u0022 ), Title( \u0022Japanese - Female 1 (jp_001)\u0022 )] jp_001,\r\n    [Icon( \u0022\uD83C\uDDEF\uD83C\uDDF5\u0022 ), Title( \u0022Japanese - Female 2 (jp_003)\u0022 )] jp_003,\r\n    [Icon( \u0022\uD83C\uDDEF\uD83C\uDDF5\u0022 ), Title( \u0022Japanese - Female 3 (jp_005)\u0022 )] jp_005,\r\n    [Icon( \u0022\uD83C\uDDEF\uD83C\uDDF5\u0022 ), Title( \u0022Japanese - Male (jp_006)\u0022 )] jp_006,\r\n\r\n    [Icon( \u0022\uD83C\uDDF0\uD83C\uDDF7\u0022 ), Title( \u0022Korean - Female 1 (kr_003)\u0022 )] kr_003,\r\n    [Icon( \u0022\uD83C\uDDF0\uD83C\uDDF7\u0022 ), Title( \u0022Korean - Male 1 (kr_002)\u0022 )] kr_002,\r\n    [Icon( \u0022\uD83C\uDDF0\uD83C\uDDF7\u0022 ), Title( \u0022Korean - Male 2 (kr_004)\u0022 )] kr_004,\r\n\r\n    [Icon( \u0022\uD83C\uDDFB\uD83C\uDDF3\u0022 ), Title( \u0022Vietnamese - Female (BV074_streaming)\u0022 )] BV074_streaming,\r\n    [Icon( \u0022\uD83C\uDDFB\uD83C\uDDF3\u0022 ), Title( \u0022Vietnamese - Male (BV075_streaming)\u0022 )] BV075_streaming,\r\n\r\n\r\n\r\n}"},{"Ident":"carsonk.tiktoktts","Path":"Editor/PropertyControlSheet.cs","FileName":"PropertyControlSheet.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":55749,"IsPrivate":false,"Code":"using System;\r\nusing Editor;\r\nusing Sandbox;\r\n\r\nnamespace TikTokTTS.Editor;\r\n\r\npublic class PropertyControlSheet : GridLayout\r\n{\r\n\tpublic SerializedObject TargetObject { get; set; }\r\n\r\n\tint rows = 0;\r\n\r\n\tpublic PropertyControlSheet() : base()\r\n\t{\r\n\t\tMargin = new Sandbox.UI.Margin( 16, 8, 16, 8 );\r\n\t\tHorizontalSpacing = 10;\r\n\t\tVerticalSpacing = 2;\r\n\t\tSetColumnStretch( 1, 2 );\r\n\t\tSetMinimumColumnWidth( 0, 120 );\r\n\t}\r\n\r\n\tpublic void AddObject( SerializedObject obj )\r\n\t{\r\n\t\tforeach ( var entry in obj )\r\n\t\t{\r\n\t\t\tif ( !entry.HasAttribute\u003CPropertyAttribute\u003E() )\r\n\t\t\t{\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif ( entry.PropertyType.Name.StartsWith( \u0022Action\u0022 ) )\r\n\t\t\t{\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tAddRow( entry );\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Add a serialized property row. This will create an editor for the row and a label.\r\n\t/// \u003C/summary\u003E\r\n\tpublic void AddRow( SerializedProperty property, float labelIndent = 0.0f )\r\n\t{\r\n\t\tvar editor = ControlWidget.Create( property );\r\n\t\tif ( editor is null )\r\n\t\t\treturn;\r\n\r\n\t\tif ( editor.IsWideMode )\r\n\t\t{\r\n\t\t\tAddCell( 0, rows\u002B\u002B, new Label( property.DisplayName ) { MinimumHeight = Theme.RowHeight, Alignment = TextFlag.LeftCenter }, 2, 1, TextFlag.LeftTop );\r\n\t\t\tvar lo = AddCell( 0, rows, Layout.Column(), 2, 1, TextFlag.LeftTop );\r\n\r\n\t\t\tlo.Margin = new Sandbox.UI.Margin( 16, 0, 0, 0 );\r\n\t\t\tlo.Add( editor );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tint cell = 0;\r\n\r\n\t\t\tvar label = AddCell( cell\u002B\u002B, rows, new Label( property.DisplayName ), 1, 1, TextFlag.LeftTop );\r\n\t\t\tlabel.MinimumHeight = Theme.RowHeight;\r\n\t\t\tlabel.Alignment = TextFlag.LeftCenter;\r\n\t\t\tlabel.SetStyles( \u0022color: #aaa;\u0022 );\r\n\r\n\t\t\tlabel.ToolTip = property.Description ?? property.DisplayName;\r\n\r\n\t\t\tif ( labelIndent \u003E 0 )\r\n\t\t\t{\r\n\t\t\t\tlabel.ContentMargins = new Sandbox.UI.Margin( labelIndent, 0, 0, 0 );\r\n\t\t\t}\r\n\r\n\t\t\tAddCell( cell\u002B\u002B, rows, editor, 1, 1, TextFlag.LeftTop );\r\n\t\t}\r\n\r\n\t\trows\u002B\u002B;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Add a layout to a double wide cell\r\n\t/// \u003C/summary\u003E\r\n\tpublic void AddLayout( Layout layout )\r\n\t{\r\n\t\tAddCell( 0, rows\u002B\u002B, layout, 2, 1, TextFlag.LeftTop );\r\n\t}\r\n\r\n}"},{"Ident":"carsonk.tiktoktts","Path":"Code/TikTokSpeech.cs","FileName":"TikTokSpeech.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":55749,"IsPrivate":false,"Code":"using System;\r\nusing Sandbox;\r\n\r\nnamespace TikTokTTS;\r\n\r\npublic class TikTokSpeech : Component\r\n{\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The text that will be spoken by the TTS.\r\n    /// \u003C/summary\u003E\r\n    [Property] public string Text { get; set; } = \u0022Hello World!\u0022;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The voice that will be used to speak the text.\r\n    /// \u003C/summary\u003E\r\n    [Property] public TikTokVoice Voice { get; set; } = TikTokVoice.en_us_002;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Whether or not the audio should be positional (based on the position of the GameObject that this component is attached to)\r\n    /// \u003C/summary\u003E\r\n    [Property] public bool PositionalAudio { get; set; } = false;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Whether or not to cache the files that are generated by the TTS. When enabled, the files will be stored in the data directory and referenced if the same call is made again.\r\n    /// \u003C/summary\u003E\r\n    [Property] public bool CacheFiles { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// An event that is fired when the TTS has started speaking.\r\n    /// \u003C/summary\u003E\r\n    [Property] public Action\u003Cstring, TikTokVoice\u003E OnSpeak { get; set; }\r\n\r\n    private string VoiceName =\u003E Enum.GetName( typeof( TikTokVoice ), Voice );\r\n    private string FileName =\u003E $\u0022tts/tts-{Text.ToLowerInvariant().UrlEncode()}-{VoiceName}.mp3\u0022;\r\n    private MusicPlayer MusicPlayer;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Speak the current text using the TTS.\r\n    /// \u003C/summary\u003E\r\n    public async void Speak()\r\n    {\r\n        MusicPlayer?.Dispose();\r\n\r\n        if ( CacheFiles )\r\n        {\r\n            if ( !FileSystem.Data.FileExists( FileName ) )\r\n            {\r\n                await TikTokTTS.Download( Text, VoiceName, FileName, FileSystem.Data );\r\n            }\r\n\r\n            MusicPlayer = MusicPlayer.Play( FileSystem.Data, FileName );\r\n        }\r\n        else\r\n        {\r\n            MusicPlayer = await TikTokTTS.Say( Text, VoiceName );\r\n            if ( MusicPlayer is null )\r\n            {\r\n                Log.Error( \u0022Failed to speak\u0022 );\r\n                return;\r\n            }\r\n        }\r\n\r\n        OnSpeak?.Invoke( Text, Voice );\r\n\r\n        MusicPlayer.ListenLocal = !PositionalAudio;\r\n    }\r\n\r\n    protected override void OnUpdate()\r\n    {\r\n        if ( MusicPlayer is not null )\r\n        {\r\n            MusicPlayer.Position = Transform.Position;\r\n        }\r\n    }\r\n\r\n}"},{"Ident":"carsonk.tiktoktts","Path":"UnitTests/UnitTest.cs","FileName":"UnitTest.cs","PackageType":"library","CodeKind":"UnitTest","AssetVersionId":55749,"IsPrivate":false,"Code":"global using Microsoft.VisualStudio.TestTools.UnitTesting;\r\n\r\n[TestClass]\r\npublic class TestInit\r\n{\r\n\t[AssemblyInitialize]\r\n\tpublic static void ClassInitialize( TestContext context )\r\n\t{\r\n\t\tSandbox.Application.InitUnitTest();\r\n\t}\r\n}\r\n"}]}