s&box Package Code Search
Search C# source code, UI razor templates, shaders, and configs across s&box packages.
link Raw Facepunch API Link: https://public.facepunch.com/sbox/code/search/1/?ident=carsonk.tiktoktts&take=20
Showing code results for query:
*
(7 total matches found)
Game
library
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sandbox;
namespace TikTokTTS;
/// <summary>
/// A static class that allows you to use the TikTokTTS API.
/// </summary>
public static class TikTokTTS
{
/// <summary>
/// The API endpoint that the library uses.
/// </summary>
public static string ENDPOINT = "https://tiktok-tts.weilnet.workers.dev";
static int currentId = 0;
/// <summary>
/// Use TikTok's TTS to speak via a MusicPlayer. This MusicPlayer will automatically dispose itself when it finishes playing.
/// </summary>
/// <param name="text">The text to be said by the TTS</param>
/// <param name="voice">The name of the voice</param>
/// <returns></returns>
public static async Task<MusicPlayer> Say( string text, string voice = "en_us_007" )
{
string fileName = $"tts-{currentId}.mp3";
currentId++;
if ( !FileSystem.Data.FileExists( fileName ) )
{
await Download( text, voice, fileName, FileSystem.Data );
}
var musicPlayer = MusicPlayer.Play( FileSystem.Data, fileName );
musicPlayer.ListenLocal = true;
return musicPlayer;
}
/// <summary>
/// Use TikTok's TTS to speak and download/save the mp3 file.
/// </summary>
/// <param name="text">The text to be said by the TTS</param>
/// <param name="voice">The name of the voice</param>
/// <param name="fileName">The file name/path it should download to (defaults to "tts-##########.mp3"</param>
/// <param name="fileSystem">The filesystem to use (defaults to FileSystem.Data)</param>
/// <returns>The file path</returns>
public static async Task<string> Download( string text, string voice = "en_us_007", string fileName = null, BaseFileSystem fileSystem = null )
{
if ( fileSystem == null ) fileSystem = FileSystem.Data;
if ( string.IsNullOrEmpty( fileName ) ) fileName = $"tts-{new Guid()}.mp3";
var content = new Dictionary<string, string> {
{ "text", text },
{ "voice", voice }
};
var headers = new Dictionary<string, string> {
{ "Content-Type", "application/json" }
};
text = text.RemoveBadCharacters();
text = text.Substring( 0, Math.Min( text.Length, 300 ) );
if ( !text.Any( x => char.IsLetterOrDigit( x ) ) ) return null;
if ( string.IsNullOrEmpty( text ) ) return null;
// Make sure folders exist
var folders = fileName.Split( '/' );
if ( folders.Length == 1 ) folders = fileName.Split( '\\' );
if ( folders.Length > 1 )
{
string path = "";
for ( int i = 0; i < folders.Length - 1; i++ )
{
path += folders[i] + "/";
if ( !fileSystem.DirectoryExists( path ) )
fileSystem.CreateDirectory( path );
}
}
try
{
if ( fileSystem.FileExists( fileName ) )
fileSystem.DeleteFile( fileName );
var response = await Http.RequestJsonAsync<TikTokTTSResponse>( "https://tiktok-tts.weilnet.workers.dev/api/generation", "POST", Http.CreateJsonContent( content ), headers );
string base64 = response.data;
byte[] mp3 = Convert.FromBase64String( base64 );
var stream = fileSystem.OpenWrite( fileName );
stream.Write( mp3, 0, mp3.Length );
stream.Close();
return fileName;
}
catch ( Exception e )
{
Log.Error( e.Message );
return null;
}
}
class TikTokTTSResponse
{
public bool success { get; set; }
public string data { get; set; }
public string error { get; set; }
}
}
UnitTest
library
using Sandbox;
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
Editor
library
using Editor;
using Sandbox;
using System.Linq;
namespace TikTokTTS.Editor;
[CustomEditor( typeof( TikTokSpeech ) )]
public class TikTokSpeechComponentEditor : ComponentEditorWidget
{
SerializedObject Target;
public TikTokSpeechComponentEditor( SerializedObject obj ) : base( obj )
{
Target = obj;
Layout = Layout.Column();
var sheet = new PropertyControlSheet();
sheet.AddObject( obj );
Layout.Add( sheet );
Layout.Spacing = 2;
MinimumHeight = 70;
var buttonPanel = Layout.Column();
buttonPanel.Margin = new Sandbox.UI.Margin( 12, 0 );
var button = new Button( "Speak", "play_arrow" )
{
Width = 200,
Clicked = () =>
{
var component = Target.Targets.First() as TikTokSpeech;
component?.Speak();
}
};
buttonPanel.Add( button );
Layout.Add( buttonPanel );
}
protected override void OnPaint()
{
}
}
Game
library
using Sandbox;
namespace TikTokTTS;
public enum TikTokVoice
{
[Icon( "๐บ๐ธ" ), Title( "US Female - Jessie (en_us_002)" )] en_us_002,
[Icon( "๐บ๐ธ" ), Title( "US Female - Empathetic (en_female_samc)" )] en_female_samc,
[Icon( "๐บ๐ธ" ), Title( "US Female - Beauty Guru (en_female_makeup)" )] en_female_makeup,
[Icon( "๐บ๐ธ" ), Title( "US Female - Bestie (en_female_richgirl)" )] en_female_richgirl,
[Icon( "๐บ๐ธ" ), Title( "US Male - Joey (en_us_006)" )] en_us_006,
[Icon( "๐บ๐ธ" ), Title( "US Male - Professor (en_us_007)" )] en_us_007,
[Icon( "๐บ๐ธ" ), Title( "US Male - Scientist (en_us_009)" )] en_us_009,
[Icon( "๐บ๐ธ" ), Title( "US Male - Confidence (en_us_010)" )] en_us_010,
[Icon( "๐บ๐ธ" ), Title( "US Male - Serious (en_male_cody)" )] en_male_cody,
[Icon( "๐บ๐ธ" ), Title( "US Male - Wacky (en_male_funny)" )] en_male_funny,
[Icon( "๐บ๐ธ" ), Title( "US Male - Story Teller (en_male_narration)" )] en_male_narration,
[Icon( "๐บ๐ธ" ), Title( "US Male - Game On (en_male_jomboy)" )] en_male_jomboy,
[Icon( "๐บ๐ธ" ), Title( "US Male - Alfred (en_male_jarvis)" )] en_male_jarvis,
[Icon( "๐ฌ๐ง" ), Title( "UK Male - Narrator (en_uk_001)" )] en_uk_001,
[Icon( "๐ฌ๐ง" ), Title( "UK Male - English (en_uk_003)" )] en_uk_003,
[Icon( "๐ฌ๐ง" ), Title( "UK Male - Olantekkers (en_male_olantekkers)" )] en_male_olantekkers,
[Icon( "๐ฆ๐บ" ), Title( "AU Female - Metro (en_au_001)" )] en_au_001,
[Icon( "๐ฆ๐บ" ), Title( "AU Female - Peaceful (en_female_emotional)" )] en_female_emotional,
[Icon( "๐ฆ๐บ" ), Title( "AU Male - Smooth (en_au_002)" )] en_au_002,
[Icon( "๐ฆ๐บ" ), Title( "AU Male - Ashmagic (en_male_ashmagic)" )] en_male_ashmagic,
[Icon( "๐" ), Title( "Festive - Cupid (en_male_cupid)" )] en_male_cupid,
[Icon( "๐
" ), Title( "Festive - Narration (en_male_santa_narration)" )] en_male_santa_narration,
[Icon( "๐
" ), Title( "Festive - Santa (en_male_santa_effect)" )] en_male_santa_effect,
[Icon( "๐ฑ" ), Title( "Disney - Scream (en_us_ghostface)" )] en_us_ghostface,
[Icon( "๐ถ" ), Title( "Disney - Chewbacca (en_us_chewbacca)" )] en_us_chewbacca,
[Icon( "๐ค" ), Title( "Disney - C3PO (en_us_c3po)" )] en_us_c3po,
[Icon( "๐ฅถ" ), Title( "Disney - Stitch (en_us_stitch)" )] en_us_stitch,
[Icon( "๐ช" ), Title( "Disney - Stormtrooper (en_us_stormtrooper)" )] en_us_stormtrooper,
[Icon( "๐" ), Title( "Disney - Rocket (en_us_rocket)" )] en_us_rocket,
[Icon( "๐ฉโ๐ฆณ" ), Title( "Disney - Madame Leota (en_female_madam_leota)" )] en_female_madam_leota,
[Icon( "๐ป" ), Title( "Disney - Ghost Host (en_male_ghosthost)" )] en_male_ghosthost,
[Icon( "โ ๏ธ" ), Title( "Disney - Pirate (en_male_pirate)" )] en_male_pirate,
[Icon( "๐งฝ" ), Title( "Silly - Trickster/Spongebob (en_male_grinch)" )] en_male_grinch,
[Icon( "๐ง" ), Title( "Silly - Mr. GoodGuy (en_male_deadpool)" )] en_male_deadpool,
[Icon( "๐" ), Title( "Silly - Lord Cringe (en_male_ukneighbor)" )] en_male_ukneighbor,
[Icon( "๐คต" ), Title( "Silly - Mr. Meticulous (en_male_ukbutler)" )] en_male_ukbutler,
[Icon( "๐ฅ" ), Title( "Silly - Debutante (en_female_shenna)" )] en_female_shenna,
[Icon( "๐คช" ), Title( "Silly - Varsity (en_female_pansino)" )] en_female_pansino,
[Icon( "๐จโ๐ฌ" ), Title( "Silly - Marty (en_male_trevor)" )] en_male_trevor,
[Icon( "๐
" ), Title( "Silly - Bae (en_female_betty)" )] en_female_betty,
[Icon( "๐ต" ), Title( "Silly - Granny (en_female_grandma)" )] en_female_grandma,
[Icon( "๐ช" ), Title( "Silly - Magician (en_male_wizard)" )] en_male_wizard,
[Icon( "๐ถ" ), Title( "Music - Alto/Cottagecore (en_female_f08_salut_damour)" )] en_female_f08_salut_damour,
[Icon( "๐ถ" ), Title( "Music - Tenor/Jingle (en_male_m03_lobby)" )] en_male_m03_lobby,
[Icon( "๐ถ" ), Title( "Music - Sunshine Soon/Toon Beat (en_male_m03_sunshine_soon)" )] en_male_m03_sunshine_soon,
[Icon( "๐ถ" ), Title( "Music - Open Mic (en_female_f08_warmy_breeze)" )] en_female_f08_warmy_breeze,
[Icon( "๐ถ" ), Title( "Music - Euphoric (en_female_ht_f08_glorious)" )] en_female_ht_f08_glorious,
[Icon( "๐ถ" ), Title( "Music - It Goes Up/Hypetrain (en_male_sing_funny_it_goes_up)" )] en_male_sing_funny_it_goes_up,
[Icon( "๐ถ" ), Title( "Music - Chipmunk/Quirky Time (en_male_m2_xhxs_m03_silly)" )] en_male_m2_xhxs_m03_silly,
[Icon( "๐ถ" ), Title( "Music - Melodrama (en_female_ht_f08_wonderful_world)" )] en_female_ht_f08_wonderful_world,
[Icon( "๐ถ" ), Title( "Parody - Pop Lullaby (en_female_f08_twinkle)" )] en_female_f08_twinkle,
[Icon( "๐ถ" ), Title( "Parody - Classic Electric (en_male_m03_classical)" )] en_male_m03_classical,
[Icon( "๐ถ" ), Title( "Parody - Cozy (en_male_m2_xhxs_m03_christmas)" )] en_male_m2_xhxs_m03_christmas,
[Icon( "๐ถ" ), Title( "Parody - Caroler (en_male_sing_deep_jingle)" )] en_male_sing_deep_jingle,
[Icon( "๐ถ" ), Title( "Parody - NYE 2023 (en_female_ht_f08_newyear)" )] en_female_ht_f08_newyear,
[Icon( "๐ถ" ), Title( "Parody - Opera (en_female_ht_f08_halloween)" )] en_female_ht_f08_halloween,
[Icon( "๐ถ" ), Title( "Parody - Thanksgiving (en_male_sing_funny_thanksgiving)" )] en_male_sing_funny_thanksgiving,
[Icon( "๐ซ๐ท" ), Title( "French - Male 1 (fr_001)" )] fr_001,
[Icon( "๐ซ๐ท" ), Title( "French - Male 2 (fr_002)" )] fr_002,
[Icon( "๐ช๐ธ" ), Title( "Spanish - Spain Male (es_002)" )] es_002,
[Icon( "๐ฒ๐ฝ" ), Title( "Spanish - MX Male (es_mx_002)" )] es_mx_002,
[Icon( "๐ง๐ท" ), Title( "Portuguese BR - Female 1 (br_001)" )] br_001,
[Icon( "๐ง๐ท" ), Title( "Portuguese BR - Female 2 (br_003)" )] br_003,
[Icon( "๐ง๐ท" ), Title( "Portuguese BR - Female 3 (br_004)" )] br_004,
[Icon( "๐ง๐ท" ), Title( "Portuguese BR - Male (br_005)" )] br_005,
[Icon( "๐ฉ๐ช" ), Title( "German - Female (de_001)" )] de_001,
[Icon( "๐ฉ๐ช" ), Title( "German - Male (de_002)" )] de_002,
[Icon( "๐ฎ๐ฉ" ), Title( "Indonesian - Female (id_001)" )] id_001,
[Icon( "๐ฏ๐ต" ), Title( "Japanese - Female 1 (jp_001)" )] jp_001,
[Icon( "๐ฏ๐ต" ), Title( "Japanese - Female 2 (jp_003)" )] jp_003,
[Icon( "๐ฏ๐ต" ), Title( "Japanese - Female 3 (jp_005)" )] jp_005,
[Icon( "๐ฏ๐ต" ), Title( "Japanese - Male (jp_006)" )] jp_006,
[Icon( "๐ฐ๐ท" ), Title( "Korean - Female 1 (kr_003)" )] kr_003,
[Icon( "๐ฐ๐ท" ), Title( "Korean - Male 1 (kr_002)" )] kr_002,
[Icon( "๐ฐ๐ท" ), Title( "Korean - Male 2 (kr_004)" )] kr_004,
[Icon( "๐ป๐ณ" ), Title( "Vietnamese - Female (BV074_streaming)" )] BV074_streaming,
[Icon( "๐ป๐ณ" ), Title( "Vietnamese - Male (BV075_streaming)" )] BV075_streaming,
}
Editor
library
using System;
using Editor;
using Sandbox;
namespace TikTokTTS.Editor;
public class PropertyControlSheet : GridLayout
{
public SerializedObject TargetObject { get; set; }
int rows = 0;
public PropertyControlSheet() : base()
{
Margin = new Sandbox.UI.Margin( 16, 8, 16, 8 );
HorizontalSpacing = 10;
VerticalSpacing = 2;
SetColumnStretch( 1, 2 );
SetMinimumColumnWidth( 0, 120 );
}
public void AddObject( SerializedObject obj )
{
foreach ( var entry in obj )
{
if ( !entry.HasAttribute<PropertyAttribute>() )
{
continue;
}
if ( entry.PropertyType.Name.StartsWith( "Action" ) )
{
continue;
}
AddRow( entry );
}
}
/// <summary>
/// Add a serialized property row. This will create an editor for the row and a label.
/// </summary>
public void AddRow( SerializedProperty property, float labelIndent = 0.0f )
{
var editor = ControlWidget.Create( property );
if ( editor is null )
return;
if ( editor.IsWideMode )
{
AddCell( 0, rows++, new Label( property.DisplayName ) { MinimumHeight = Theme.RowHeight, Alignment = TextFlag.LeftCenter }, 2, 1, TextFlag.LeftTop );
var lo = AddCell( 0, rows, Layout.Column(), 2, 1, TextFlag.LeftTop );
lo.Margin = new Sandbox.UI.Margin( 16, 0, 0, 0 );
lo.Add( editor );
}
else
{
int cell = 0;
var label = AddCell( cell++, rows, new Label( property.DisplayName ), 1, 1, TextFlag.LeftTop );
label.MinimumHeight = Theme.RowHeight;
label.Alignment = TextFlag.LeftCenter;
label.SetStyles( "color: #aaa;" );
label.ToolTip = property.Description ?? property.DisplayName;
if ( labelIndent > 0 )
{
label.ContentMargins = new Sandbox.UI.Margin( labelIndent, 0, 0, 0 );
}
AddCell( cell++, rows, editor, 1, 1, TextFlag.LeftTop );
}
rows++;
}
/// <summary>
/// Add a layout to a double wide cell
/// </summary>
public void AddLayout( Layout layout )
{
AddCell( 0, rows++, layout, 2, 1, TextFlag.LeftTop );
}
}
Game
library
using System;
using Sandbox;
namespace TikTokTTS;
public class TikTokSpeech : Component
{
/// <summary>
/// The text that will be spoken by the TTS.
/// </summary>
[Property] public string Text { get; set; } = "Hello World!";
/// <summary>
/// The voice that will be used to speak the text.
/// </summary>
[Property] public TikTokVoice Voice { get; set; } = TikTokVoice.en_us_002;
/// <summary>
/// Whether or not the audio should be positional (based on the position of the GameObject that this component is attached to)
/// </summary>
[Property] public bool PositionalAudio { get; set; } = false;
/// <summary>
/// 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.
/// </summary>
[Property] public bool CacheFiles { get; set; } = true;
/// <summary>
/// An event that is fired when the TTS has started speaking.
/// </summary>
[Property] public Action<string, TikTokVoice> OnSpeak { get; set; }
private string VoiceName => Enum.GetName( typeof( TikTokVoice ), Voice );
private string FileName => $"tts/tts-{Text.ToLowerInvariant().UrlEncode()}-{VoiceName}.mp3";
private MusicPlayer MusicPlayer;
/// <summary>
/// Speak the current text using the TTS.
/// </summary>
public async void Speak()
{
MusicPlayer?.Dispose();
if ( CacheFiles )
{
if ( !FileSystem.Data.FileExists( FileName ) )
{
await TikTokTTS.Download( Text, VoiceName, FileName, FileSystem.Data );
}
MusicPlayer = MusicPlayer.Play( FileSystem.Data, FileName );
}
else
{
MusicPlayer = await TikTokTTS.Say( Text, VoiceName );
if ( MusicPlayer is null )
{
Log.Error( "Failed to speak" );
return;
}
}
OnSpeak?.Invoke( Text, Voice );
MusicPlayer.ListenLocal = !PositionalAudio;
}
protected override void OnUpdate()
{
if ( MusicPlayer is not null )
{
MusicPlayer.Position = Transform.Position;
}
}
}
UnitTest
library
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
[AssemblyInitialize]
public static void ClassInitialize( TestContext context )
{
Sandbox.Application.InitUnitTest();
}
}
Debug: View Raw JSON Response
{
"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/// <summary>\r\n/// A static class that allows you to use the TikTokTTS API.\r\n/// </summary>\r\npublic static class TikTokTTS\r\n{\r\n /// <summary>\r\n /// The API endpoint that the library uses.\r\n /// </summary>\r\n public static string ENDPOINT = \"https://tiktok-tts.weilnet.workers.dev\";\r\n\r\n static int currentId = 0;\r\n\r\n /// <summary>\r\n /// Use TikTok's TTS to speak via a MusicPlayer. This MusicPlayer will automatically dispose itself when it finishes playing.\r\n /// </summary>\r\n /// <param name=\"text\">The text to be said by the TTS</param>\r\n /// <param name=\"voice\">The name of the voice</param>\r\n /// <returns></returns>\r\n public static async Task<MusicPlayer> Say( string text, string voice = \"en_us_007\" )\r\n {\r\n string fileName = $\"tts-{currentId}.mp3\";\r\n currentId++;\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 /// <summary>\r\n /// Use TikTok's TTS to speak and download/save the mp3 file.\r\n /// </summary>\r\n /// <param name=\"text\">The text to be said by the TTS</param>\r\n /// <param name=\"voice\">The name of the voice</param>\r\n /// <param name=\"fileName\">The file name/path it should download to (defaults to \"tts-##########.mp3\"</param>\r\n /// <param name=\"fileSystem\">The filesystem to use (defaults to FileSystem.Data)</param>\r\n /// <returns>The file path</returns>\r\n public static async Task<string> Download( string text, string voice = \"en_us_007\", string fileName = null, BaseFileSystem fileSystem = null )\r\n {\r\n if ( fileSystem == null ) fileSystem = FileSystem.Data;\r\n if ( string.IsNullOrEmpty( fileName ) ) fileName = $\"tts-{new Guid()}.mp3\";\r\n\r\n var content = new Dictionary<string, string> {\r\n { \"text\", text },\r\n { \"voice\", voice }\r\n };\r\n var headers = new Dictionary<string, string> {\r\n { \"Content-Type\", \"application/json\" }\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 => 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( '/' );\r\n if ( folders.Length == 1 ) folders = fileName.Split( '\\\\' );\r\n if ( folders.Length > 1 )\r\n {\r\n string path = \"\";\r\n for ( int i = 0; i < folders.Length - 1; i++ )\r\n {\r\n path += folders[i] + \"/\";\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<TikTokTTSResponse>( \"https://tiktok-tts.weilnet.workers.dev/api/generation\", \"POST\", 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( \"Speak\", \"play_arrow\" )\r\n\t\t{\r\n\t\t\tWidth = 200,\r\n\t\t\tClicked = () =>\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( \"\ud83c\uddfa\ud83c\uddf8\" ), Title( \"US Female - Jessie (en_us_002)\" )] en_us_002,\r\n [Icon( \"\ud83c\uddfa\ud83c\uddf8\" ), Title( \"US Female - Empathetic (en_female_samc)\" )] en_female_samc,\r\n [Icon( \"\ud83c\uddfa\ud83c\uddf8\" ), Title( \"US Female - Beauty Guru (en_female_makeup)\" )] en_female_makeup,\r\n [Icon( \"\ud83c\uddfa\ud83c\uddf8\" ), Title( \"US Female - Bestie (en_female_richgirl)\" )] en_female_richgirl,\r\n\r\n [Icon( \"\ud83c\uddfa\ud83c\uddf8\" ), Title( \"US Male - Joey (en_us_006)\" )] en_us_006,\r\n [Icon( \"\ud83c\uddfa\ud83c\uddf8\" ), Title( \"US Male - Professor (en_us_007)\" )] en_us_007,\r\n [Icon( \"\ud83c\uddfa\ud83c\uddf8\" ), Title( \"US Male - Scientist (en_us_009)\" )] en_us_009,\r\n [Icon( \"\ud83c\uddfa\ud83c\uddf8\" ), Title( \"US Male - Confidence (en_us_010)\" )] en_us_010,\r\n [Icon( \"\ud83c\uddfa\ud83c\uddf8\" ), Title( \"US Male - Serious (en_male_cody)\" )] en_male_cody,\r\n [Icon( \"\ud83c\uddfa\ud83c\uddf8\" ), Title( \"US Male - Wacky (en_male_funny)\" )] en_male_funny,\r\n [Icon( \"\ud83c\uddfa\ud83c\uddf8\" ), Title( \"US Male - Story Teller (en_male_narration)\" )] en_male_narration,\r\n [Icon( \"\ud83c\uddfa\ud83c\uddf8\" ), Title( \"US Male - Game On (en_male_jomboy)\" )] en_male_jomboy,\r\n [Icon( \"\ud83c\uddfa\ud83c\uddf8\" ), Title( \"US Male - Alfred (en_male_jarvis)\" )] en_male_jarvis,\r\n\r\n [Icon( \"\ud83c\uddec\ud83c\udde7\" ), Title( \"UK Male - Narrator (en_uk_001)\" )] en_uk_001,\r\n [Icon( \"\ud83c\uddec\ud83c\udde7\" ), Title( \"UK Male - English (en_uk_003)\" )] en_uk_003,\r\n [Icon( \"\ud83c\uddec\ud83c\udde7\" ), Title( \"UK Male - Olantekkers (en_male_olantekkers)\" )] en_male_olantekkers,\r\n\r\n [Icon( \"\ud83c\udde6\ud83c\uddfa\" ), Title( \"AU Female - Metro (en_au_001)\" )] en_au_001,\r\n [Icon( \"\ud83c\udde6\ud83c\uddfa\" ), Title( \"AU Female - Peaceful (en_female_emotional)\" )] en_female_emotional,\r\n [Icon( \"\ud83c\udde6\ud83c\uddfa\" ), Title( \"AU Male - Smooth (en_au_002)\" )] en_au_002,\r\n [Icon( \"\ud83c\udde6\ud83c\uddfa\" ), Title( \"AU Male - Ashmagic (en_male_ashmagic)\" )] en_male_ashmagic,\r\n\r\n [Icon( \"\ud83d\udc98\" ), Title( \"Festive - Cupid (en_male_cupid)\" )] en_male_cupid,\r\n [Icon( \"\ud83c\udf85\" ), Title( \"Festive - Narration (en_male_santa_narration)\" )] en_male_santa_narration,\r\n [Icon( \"\ud83c\udf85\" ), Title( \"Festive - Santa (en_male_santa_effect)\" )] en_male_santa_effect,\r\n\r\n [Icon( \"\ud83d\ude31\" ), Title( \"Disney - Scream (en_us_ghostface)\" )] en_us_ghostface,\r\n [Icon( \"\ud83d\udc36\" ), Title( \"Disney - Chewbacca (en_us_chewbacca)\" )] en_us_chewbacca,\r\n [Icon( \"\ud83e\udd16\" ), Title( \"Disney - C3PO (en_us_c3po)\" )] en_us_c3po,\r\n [Icon( \"\ud83e\udd76\" ), Title( \"Disney - Stitch (en_us_stitch)\" )] en_us_stitch,\r\n [Icon( \"\ud83e\ude96\" ), Title( \"Disney - Stormtrooper (en_us_stormtrooper)\" )] en_us_stormtrooper,\r\n [Icon( \"\ud83d\ude80\" ), Title( \"Disney - Rocket (en_us_rocket)\" )] en_us_rocket,\r\n [Icon( \"\ud83d\udc69\u200d\ud83e\uddb3\" ), Title( \"Disney - Madame Leota (en_female_madam_leota)\" )] en_female_madam_leota,\r\n [Icon( \"\ud83d\udc7b\" ), Title( \"Disney - Ghost Host (en_male_ghosthost)\" )] en_male_ghosthost,\r\n [Icon( \"\u2620\ufe0f\" ), Title( \"Disney - Pirate (en_male_pirate)\" )] en_male_pirate,\r\n\r\n [Icon( \"\ud83e\uddfd\" ), Title( \"Silly - Trickster/Spongebob (en_male_grinch)\" )] en_male_grinch,\r\n [Icon( \"\ud83e\uddd4\" ), Title( \"Silly - Mr. GoodGuy (en_male_deadpool)\" )] en_male_deadpool,\r\n [Icon( \"\ud83d\ude4d\" ), Title( \"Silly - Lord Cringe (en_male_ukneighbor)\" )] en_male_ukneighbor,\r\n [Icon( \"\ud83e\udd35\" ), Title( \"Silly - Mr. Meticulous (en_male_ukbutler)\" )] en_male_ukbutler,\r\n [Icon( \"\ud83e\udd42\" ), Title( \"Silly - Debutante (en_female_shenna)\" )] en_female_shenna,\r\n [Icon( \"\ud83e\udd2a\" ), Title( \"Silly - Varsity (en_female_pansino)\" )] en_female_pansino,\r\n [Icon( \"\ud83d\udc68\u200d\ud83d\udd2c\" ), Title( \"Silly - Marty (en_male_trevor)\" )] en_male_trevor,\r\n [Icon( \"\ud83d\ude45\" ), Title( \"Silly - Bae (en_female_betty)\" )] en_female_betty,\r\n [Icon( \"\ud83d\udc75\" ), Title( \"Silly - Granny (en_female_grandma)\" )] en_female_grandma,\r\n [Icon( \"\ud83e\ude84\" ), Title( \"Silly - Magician (en_male_wizard)\" )] en_male_wizard,\r\n\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Music - Alto/Cottagecore (en_female_f08_salut_damour)\" )] en_female_f08_salut_damour,\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Music - Tenor/Jingle (en_male_m03_lobby)\" )] en_male_m03_lobby,\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Music - Sunshine Soon/Toon Beat (en_male_m03_sunshine_soon)\" )] en_male_m03_sunshine_soon,\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Music - Open Mic (en_female_f08_warmy_breeze)\" )] en_female_f08_warmy_breeze,\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Music - Euphoric (en_female_ht_f08_glorious)\" )] en_female_ht_f08_glorious,\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Music - It Goes Up/Hypetrain (en_male_sing_funny_it_goes_up)\" )] en_male_sing_funny_it_goes_up,\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Music - Chipmunk/Quirky Time (en_male_m2_xhxs_m03_silly)\" )] en_male_m2_xhxs_m03_silly,\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Music - Melodrama (en_female_ht_f08_wonderful_world)\" )] en_female_ht_f08_wonderful_world,\r\n\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Parody - Pop Lullaby (en_female_f08_twinkle)\" )] en_female_f08_twinkle,\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Parody - Classic Electric (en_male_m03_classical)\" )] en_male_m03_classical,\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Parody - Cozy (en_male_m2_xhxs_m03_christmas)\" )] en_male_m2_xhxs_m03_christmas,\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Parody - Caroler (en_male_sing_deep_jingle)\" )] en_male_sing_deep_jingle,\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Parody - NYE 2023 (en_female_ht_f08_newyear)\" )] en_female_ht_f08_newyear,\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Parody - Opera (en_female_ht_f08_halloween)\" )] en_female_ht_f08_halloween,\r\n [Icon( \"\ud83c\udfb6\" ), Title( \"Parody - Thanksgiving (en_male_sing_funny_thanksgiving)\" )] en_male_sing_funny_thanksgiving,\r\n\r\n [Icon( \"\ud83c\uddeb\ud83c\uddf7\" ), Title( \"French - Male 1 (fr_001)\" )] fr_001,\r\n [Icon( \"\ud83c\uddeb\ud83c\uddf7\" ), Title( \"French - Male 2 (fr_002)\" )] fr_002,\r\n\r\n [Icon( \"\ud83c\uddea\ud83c\uddf8\" ), Title( \"Spanish - Spain Male (es_002)\" )] es_002,\r\n\r\n [Icon( \"\ud83c\uddf2\ud83c\uddfd\" ), Title( \"Spanish - MX Male (es_mx_002)\" )] es_mx_002,\r\n\r\n [Icon( \"\ud83c\udde7\ud83c\uddf7\" ), Title( \"Portuguese BR - Female 1 (br_001)\" )] br_001,\r\n [Icon( \"\ud83c\udde7\ud83c\uddf7\" ), Title( \"Portuguese BR - Female 2 (br_003)\" )] br_003,\r\n [Icon( \"\ud83c\udde7\ud83c\uddf7\" ), Title( \"Portuguese BR - Female 3 (br_004)\" )] br_004,\r\n [Icon( \"\ud83c\udde7\ud83c\uddf7\" ), Title( \"Portuguese BR - Male (br_005)\" )] br_005,\r\n\r\n [Icon( \"\ud83c\udde9\ud83c\uddea\" ), Title( \"German - Female (de_001)\" )] de_001,\r\n [Icon( \"\ud83c\udde9\ud83c\uddea\" ), Title( \"German - Male (de_002)\" )] de_002,\r\n\r\n [Icon( \"\ud83c\uddee\ud83c\udde9\" ), Title( \"Indonesian - Female (id_001)\" )] id_001,\r\n\r\n [Icon( \"\ud83c\uddef\ud83c\uddf5\" ), Title( \"Japanese - Female 1 (jp_001)\" )] jp_001,\r\n [Icon( \"\ud83c\uddef\ud83c\uddf5\" ), Title( \"Japanese - Female 2 (jp_003)\" )] jp_003,\r\n [Icon( \"\ud83c\uddef\ud83c\uddf5\" ), Title( \"Japanese - Female 3 (jp_005)\" )] jp_005,\r\n [Icon( \"\ud83c\uddef\ud83c\uddf5\" ), Title( \"Japanese - Male (jp_006)\" )] jp_006,\r\n\r\n [Icon( \"\ud83c\uddf0\ud83c\uddf7\" ), Title( \"Korean - Female 1 (kr_003)\" )] kr_003,\r\n [Icon( \"\ud83c\uddf0\ud83c\uddf7\" ), Title( \"Korean - Male 1 (kr_002)\" )] kr_002,\r\n [Icon( \"\ud83c\uddf0\ud83c\uddf7\" ), Title( \"Korean - Male 2 (kr_004)\" )] kr_004,\r\n\r\n [Icon( \"\ud83c\uddfb\ud83c\uddf3\" ), Title( \"Vietnamese - Female (BV074_streaming)\" )] BV074_streaming,\r\n [Icon( \"\ud83c\uddfb\ud83c\uddf3\" ), Title( \"Vietnamese - Male (BV075_streaming)\" )] 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<PropertyAttribute>() )\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( \"Action\" ) )\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/// <summary>\r\n\t/// Add a serialized property row. This will create an editor for the row and a label.\r\n\t/// </summary>\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++, 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++, 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( \"color: #aaa;\" );\r\n\r\n\t\t\tlabel.ToolTip = property.Description ?? property.DisplayName;\r\n\r\n\t\t\tif ( labelIndent > 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++, rows, editor, 1, 1, TextFlag.LeftTop );\r\n\t\t}\r\n\r\n\t\trows++;\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Add a layout to a double wide cell\r\n\t/// </summary>\r\n\tpublic void AddLayout( Layout layout )\r\n\t{\r\n\t\tAddCell( 0, rows++, 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 /// <summary>\r\n /// The text that will be spoken by the TTS.\r\n /// </summary>\r\n [Property] public string Text { get; set; } = \"Hello World!\";\r\n\r\n /// <summary>\r\n /// The voice that will be used to speak the text.\r\n /// </summary>\r\n [Property] public TikTokVoice Voice { get; set; } = TikTokVoice.en_us_002;\r\n\r\n /// <summary>\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 /// </summary>\r\n [Property] public bool PositionalAudio { get; set; } = false;\r\n\r\n /// <summary>\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 /// </summary>\r\n [Property] public bool CacheFiles { get; set; } = true;\r\n\r\n /// <summary>\r\n /// An event that is fired when the TTS has started speaking.\r\n /// </summary>\r\n [Property] public Action<string, TikTokVoice> OnSpeak { get; set; }\r\n\r\n private string VoiceName => Enum.GetName( typeof( TikTokVoice ), Voice );\r\n private string FileName => $\"tts/tts-{Text.ToLowerInvariant().UrlEncode()}-{VoiceName}.mp3\";\r\n private MusicPlayer MusicPlayer;\r\n\r\n /// <summary>\r\n /// Speak the current text using the TTS.\r\n /// </summary>\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( \"Failed to speak\" );\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"
}
]
}