I'm creating a MacOS application that generates dialog files from a tab-delimited text file. The text file is formatted (output filename) [tab] (text to be spoken to that file). I got the program working perfectly running in the main thread, but this blocked the UI, of course. When I moved the meat of the application to a User Initiated thread, I unblocked the UI, but now it occasionally and randomly speaks a line out the speaker instead of to a file. A file is still created, but it's of zero duration. If the script contains 1,000 lines, it might get through the whole thing perfectly, or it might "speak" ten or more of them out the speaker. It's almost always different lines each time.
This is the code that speaks to a file:
// Set URL to save file to disk
if let speechFileURL = NSURL(string: speechFilePath) {
speechSynth.startSpeakingString(spokenText, toURL: speechFileURL) // Speak to file
}
else {
dialogAlert("Invalid File: ", text: speechFileName)
}
while(NSSpeechSynthesizer.isAnyApplicationSpeaking() == true) {
if self.gUserClickedStop {
speechSynth.stopSpeaking()
self.gIsSpeaking = false
break
}
}
This is the call to the function that parses the script and generates the speech files:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) { [unowned self] in
self.parseTheScript()
}
As mentioned above, calling parseTheScript() without wrapping it in the Grand Central Dispatch call makes it work perfectly, except for the blocking issue.
I'm new to programming in Swift and the idea of multithreaded programming, so I'm sure I'm doing something wrong, but I'm stumped as to how to fix this problem.
Thank you in advance for your help.
Edit to add...
This is the entire function that parses the text file and generates the dialog files:
// This opens the text file and generates the spoken audio files
func parseTheScript() {
let filemgr = NSFileManager.defaultManager()
print("Chosen path: \(scriptFileTextField.stringValue)") // debug
if filemgr.fileExistsAtPath(scriptFileTextField.stringValue) {
print("File exists")
do {
outputFilename.stringValue = ""
scriptToSpeak.stringValue = ""
outputFilename.hidden = false
scriptToSpeak.hidden = false
progressBar.minValue = 0
progressBar.doubleValue = 0.0
progressBar.hidden = false
filenameLabel.hidden = false
let text = try String(contentsOfFile: scriptFileTextField.stringValue, encoding: NSUTF8StringEncoding)
let lines: [String] = text.componentsSeparatedByString("\n")
var progressEndValue = Double(lines.count)
progressBar.maxValue = progressEndValue
let speechSynth = NSSpeechSynthesizer.init()
let theVoice = gVoiceList[voiceListPopup.indexOfSelectedItem - 1] // Not sure why it's off by one
speechSynth.setVoice(theVoice)
for line in lines {
let thisLine: [String] = line.componentsSeparatedByString("\t") // Split the line at the tab character
if (thisLine.count == 2) { // Did the line split into two parts?
let speechFileName = thisLine[0]
let spokenText = thisLine[1]
outputFilename.stringValue = speechFileName
scriptToSpeak.stringValue = spokenText
let speechFilePath = destinationFolderTextField.stringValue + speechFileName + ".aif" // Build the path string for the output speech file
print("Filename: \(speechFilePath)")
print("SpokenText: \(spokenText)")
if(gSpeakToFile) {
// Set URL to save file to disk
if let speechFileURL = NSURL(string: speechFilePath) {
speechSynth.startSpeakingString(spokenText, toURL: speechFileURL) // Speak to file
}
else {
dialogAlert("Invalid File: ", text: speechFileName)
}
}
else {
speechSynth.startSpeakingString(spokenText) // Speak to audio output
}
while(NSSpeechSynthesizer.isAnyApplicationSpeaking() == true) {
if self.gUserClickedStop {
speechSynth.stopSpeaking()
self.gIsSpeaking = false
break
}
}
progressBar.incrementBy(1.0)
}
if gUserClickedStop {
gUserClickedStop = false
gIsSpeaking = false
progressEndValue = 0
generateButton.title = "Start"
break
}
}
gIsSpeaking = false
progressBar.doubleValue = progressEndValue
generateButton.title = "Start"
filenameLabel.hidden = true
outputFilename.stringValue = ""
scriptToSpeak.stringValue = ""
} catch let error as NSError {
dialogAlert("Error:", text: String(error.localizedDescription))
print("Error: \(error)")
}
} else {
print("File does not exist")
}
}
Related
I got two JSON files that I'm processing:
sorted/apr-total2.json
sorted/may-total2.json
There are files for other months of the year, but for debugging purposes I'm focusing on these two because they are causing the problem.
They contain simple data in the following format:
[
["John", 1403],
["Peter", 1159],
...
]
However, John is available only in sorted/apr-total2.json file and not in sorted/may-total2.json.
My code is the following:
let months = ["apr", "may"];
let dataToSort = {};
function getDataFromJSONFile(month) {
let jsonData = fs.readFileSync(`sorted/${month}-total2.json`);
let convertedData = JSON.parse(jsonData);
return convertedData;
}
function arrayToObject(data) {
data.forEach((user) => {
dataToSort[user[0].toString()] = user[1];
});
return dataToSort;
}
for (let i in months) {
let currentMonth = months[i];
console.log("Current month is " + currentMonth);
let getDataForMonth = getDataFromJSONFile(currentMonth);
let convertedMonth = arrayToObject(getDataForMonth);
if (convertedMonth.hasOwnProperty("John") == true) {
console.log(true);
}
}
My output is the following:
Current month is apr
true
Current month is may
true
This isn't correct, since user John isn't available in sorted/may-total2.json. So why is this happening? It seems that the object convertedMonth is causing the issue.
If I add the following code at the end of the for loop and delete the ConvertedMonth object properties:
for (let item in convertedMonth) {
delete convertedMonth[item];
}
It works as intended:
Current month is apr
true
Current month is may
I want to know why this object is causing the issue, because I reused the same code in another place of my project.
Your arrayToObject function uses a single global dataToSort, so any entries will be accumulated and overwritten there. See:
> let dataToSort = {};
> function arrayToObject(data) {
... data.forEach((user) => {
... dataToSort[user[0].toString()] = user[1];
... });
... return dataToSort;
... }
> arrayToObject([["foo", 123]])
{ foo: 123 }
> arrayToObject([["foo", 456], ["bar", 567]])
{ foo: 456, bar: 567 }
Your program simplifies to something like
let months = ["apr", "may"];
for (let currentMonth of months) {
console.log("Current month is " + currentMonth);
let jsonData = JSON.parse(fs.readFileSync(`sorted/${month}-total2.json`));
let convertedMonth = Object.fromEntries(jsonData);
if (convertedMonth["John"]) {
console.log(true);
}
}
which
uses for..of for correct and simpler iteration over the months
uses Object.fromEntries to convert an array of 2-arrays into a plain object (which is what your arrayToObject did)
const Discord = require('discord.js')
const prefix1 = '*add'
const prefix2 = '*what'
const prefix3 = '*remove'
const prefix4 = '*search'
const bot4 = new Discord.Client();
let a = []
let fakea = []
bot4.on('message', msg => {
if(msg.member.hasPermission('ADMINISTRATOR')){
if(msg.content.startsWith(prefix1)){
let splited = msg.content.split(' ')
let unchanged = msg.content.split(' ')
splited.splice('*info', 1)
splited.splice(msg.content[1], 1)
splited.splice(msg.content[2], 1)
let c = splited.join(' ')
b = {
namer: unchanged[1],
imformation: unchanged[2],
description: c
}
if(fakea.includes(unchanged[1])){
msg.channel.send('It already exists')
} else {
a.push(b)
fakea.push(unchanged[1])
}
console.log(a)
}
if(msg.content.startsWith(prefix3)){
let armay = msg.content.split(' ')
console.log(a)
console.log(fakea)
if(armay.length != 2){
msg.channel.send(`You have either less or more than two words. That either means you wrote *add on it's own or you had more than one word that you put with the command`)
} else {
if(!fakea.includes(armay[1])){
msg.channel.send(`That doesn't exist. You can't delete something that doesn't exist.`)
} else {
let fakeafind = fakea.find(plot => plot === armay[1])
let afind = a.find(plote => plote.namer === armay[1])
fakea.splice(fakeafind, 1)
a.splice(afind, 1)
}
console.log(a)
console.log(fakea)
}
}
if(msg.content.startsWith(prefix2)){
let coolon = fakea.join('\n')
let don = `_________\n[\n${coolon}\n]\n_________`
const notbot3embed = new Discord.MessageEmbed()
.setTitle('Everything you can search')
.setColor('15DD7C')
.addField('The things you can search', don)
msg.channel.send(notbot3embed)
}
if(msg.content.startsWith(prefix4)){
let mayi = msg.content.split(' ')
if(mayi.length != 2){
msg.channel.send(`You have either less or more than two words. That either means you wrote *search on it's own or you had more than one word that you put with the command`)
} else {
if(fakea.includes(mayi[1])){
let ft = a.filter(thing => thing.namer === mayi[1])
console.log(ft)
let secot = ft.namer
let thirt = ft.imformation
let fort = ft.description
const someembed = new Discord.MessageEmbed()
.setTitle(secot)
.setColor('FF4200')
.addField(thirt, fort)
msg.channel.send(someembed)
} else {
msg.channel.send('This is not a searchable term. Use *what to see the terms that are there.')
}
}
}
}
})
bot4.login(process.env.token4)
I wrote all of it because you would be confused about the properties if I didn't. In the last part I try to get the properties of the object with the name after '*search'. Then I want to put it an embed. Here's the problem I get. On the embed all three of the things say undefined. How do I fix this
If you're confused what I'm trying to do here's what I'm trying to do. I'm trying to make a system that you can put search things(I don't know how to frase it), remove them, check which ones exist and search something. Most of it is working. But in the search part it says for all of them in the embed undefined.
I found out that you need to use find instead of filter. Find works.
Ok, having some problems with Apple's command center here, playing background audio/on lock screen and cant understand why. Seems pretty simple but I don't even have the episode information displaying reliably in command center, definitely cant play/pause.
First I start the Audio session:
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: .mixWithOthers)
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
} catch {
print(error)
}
then I set up my command center buttons to be enabled explicitly:
UIApplication.shared.beginReceivingRemoteControlEvents()
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.skipForwardCommand.isEnabled = true
commandCenter.skipBackwardCommand.isEnabled = true
commandCenter.nextTrackCommand.isEnabled = true
commandCenter.togglePlayPauseCommand.isEnabled = true
// commandCenter.previousTrackCommand.isEnabled = true
// commandCenter.togglePlayPauseCommand.isEnabled = true
commandCenter.togglePlayPauseCommand.addTarget(self, action:#selector(togglePlayPause))
// commandCenter.nextTrackCommand.addTarget(self, action:#selector(nextTrackForward))
// commandCenter.previousTrackCommand.addTarget(self, action:#selector(nextTrackBack))
commandCenter.skipForwardCommand.addTarget(self, action:#selector(ffPressed))
commandCenter.skipBackwardCommand.addTarget(self, action:#selector(rwPressed))
Here is the lldb plist keys if that matters for audio:
What's wrong here?
Maybe your question is old but someone could need this too, so this is how I did it :
Swift 5
let player = AVPlayer()
func commandCenter () {
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.addTarget { (commandEvent) -> MPRemoteCommandHandlerStatus in self.play(); return .success }
commandCenter.pauseCommand.addTarget { (commandEvent) -> MPRemoteCommandHandlerStatus in self.pause(); return .success }
}
func pause () {
player.pause ()
print("paused")
}
func play () {
player.play()
print("play")
}
You don't need to enable commandCenter.playCommand to true
Last year my colleague helped to build a script for Indesign.
Subsequently, after a system update we no longer have the script as Indesign CS6 was reinstalled, all we have is a version as below.
Using this code in Adobe Indesign to export each text frame that begins with a particular Paragraph stylesheet "PRODUCT HEADING" however I get an error message when I run the script...
Script is based on the ExportAllStories.jsx bundled with InDesign, plus a few mods found online.
//ExportAllStories.jsx
//An InDesign CS6 JavaScript
/*
###BUILDINFO### "ExportAllStories.jsx" 3.0.0 15 December 2009
*/
//Exports all stories in an InDesign document in a specified text format.
//
//For more on InDesign scripting, go to http://www.adobe.com/products/indesign/scripting/index.html
//or visit the InDesign Scripting User to User forum at http://www.adobeforums.com
//
main();
function main(){
//Make certain that user interaction (display of dialogs, etc.) is turned on.
app.scriptPreferences.userInteractionLevel = UserInteractionLevels.interactWithAll;
if(app.documents.length != 0){
if (app.activeDocument.stories.length != 0){
myDisplayDialog();
}
else{
alert("The document does not contain any text. Please open a document containing text and try again.");
}
}
else{
alert("No documents are open. Please open a document and try again.");
}
}
function myDisplayDialog(){
with(myDialog = app.dialogs.add({name:"ExportAllStories"})){
//Add a dialog column.
myDialogColumn = dialogColumns.add()
with(myDialogColumn){
with(borderPanels.add()){
staticTexts.add({staticLabel:"Export as:"});
with(myExportFormatButtons = radiobuttonGroups.add()){
radiobuttonControls.add({staticLabel:"Text Only", checkedState:true});
radiobuttonControls.add({staticLabel:"RTF"});
radiobuttonControls.add({staticLabel:"InDesign Tagged Text"});
}
}
}
myReturn = myDialog.show();
if (myReturn == true){
//Get the values from the dialog box.
myExportFormat = myExportFormatButtons.selectedButton;
myDialog.destroy;
myFolder= Folder.selectDialog ("Choose a Folder");
if((myFolder != null)&&(app.activeDocument.stories.length !=0)){
myExportAllStories(myExportFormat, myFolder);
}
}
else{
myDialog.destroy();
}
}
}
//myExportStories function takes care of exporting the stories.
//myExportFormat is a number from 0-2, where 0 = text only, 1 = rtf, and 3 = tagged text.
//myFolder is a reference to the folder in which you want to save your files.
function myExportAllStories(myExportFormat, myFolder){
for(myCounter = 0; myCounter < app.activeDocument.stories.length; myCounter++){
myStory = app.activeDocument.stories.item(myCounter);
myID = myStory.id;
switch(myExportFormat){
case 0:
myFormat = ExportFormat.textType;
myExtension = ".txt"
break;
case 1:
myFormat = ExportFormat.RTF;
myExtension = ".rtf"
break;
case 2:
myFormat = ExportFormat.taggedText;
myExtension = ".txt"
break;
}
if (myStory.paragraphs[0].appliedParagraphStyle.name == "PRODUCT HEADING"){
myFileName = myFileName.replace(/\s*$/,' ');
myFileName2 = myFileName.replace(/\//g, ' ');
myFilePath = myFolder + "/" + myFileName2;
myFile = new File(myFilePath);
myStory.exportFile(myFormat, myFile);
}
}
}
This results in an error on
if (myStory.paragraphs[0].appliedParagraphStyle.name == "PRODUCT HEADING"){
Any advice would be appreciated.
There is definitely a block of text with the style PRODUCT HEADING (all caps) in the Indesign File. We run Indesign CS6 as previous
thanks!
Your problem is most likely with this part: myStory.paragraphs[0]. If the story has no paragraphs this will give you an error.
You could add a condition before running this line, like this for example:
if(myStory.paragraphs.length){
if (myStory.paragraphs[0].appliedParagraphStyle.name == "PRODUCT HEADING"){
myFileName = myFileName.replace(/\s*$/,' ');
myFileName2 = myFileName.replace(/\//g, ' ');
myFilePath = myFolder + "/" + myFileName2;
myFile = new File(myFilePath);
myStory.exportFile(myFormat, myFile);
}
}
I was wondering what the simplest and cleanest to read a text file into an array of strings is in swift.
Text file:
line 1
line 2
line 3
line 4
Into an array like this:
var array = ["line 1","line 2","line 3","line 4"]
I would also like to know how to do a similar thing into struct like this:
Struct struct{
var name: String!
var email: String!
}
so take a text file and put it into struct's in an array.
Thanks for the help!
First you must read the file:
let text = String(contentsOfFile: someFile, encoding: NSUTF8StringEncoding, error: nil)
Then you separate it by line using the componentsSeparatedByString method:
let lines : [String] = text.componentsSeparatedByString("\n")
Updated for Swift 3
var arrayOfStrings: [String]?
do {
// This solution assumes you've got the file in your bundle
if let path = Bundle.main.path(forResource: "YourTextFilename", ofType: "txt"){
let data = try String(contentsOfFile:path, encoding: String.Encoding.utf8)
arrayOfStrings = data.components(separatedBy: "\n")
print(arrayOfStrings)
}
} catch let err as NSError {
// do something with Error
print(err)
}
Updated for Swift 5:
The const path contains the file path.
do {
let path: String = "file.txt"
let file = try String(contentsOfFile: path)
let text: [String] = file.components(separatedBy: "\n")
} catch let error {
Swift.print("Fatal Error: \(error.localizedDescription)")
}
If you want to print what's inside of file.txt line by line:
for line in text {
Swift.print(line)
}
Here is a way to convert a string to an array(Once you read in the text):
var myString = "Here is my string"
var myArray : [String] = myString.componentsSeparatedByString(" ")
This returns a string array with the following values: ["Here", "is", "my", "string"]
Swift 4:
do {
let contents = try String(contentsOfFile: file, encoding: String.Encoding.utf8)
let lines : [String] = contents.components(separatedBy: "\n")
} catch let error as NSError {
print(error.localizedDescription)
}
In Swift 3 for me worked like below:
Import Foundation
let lines : [String] = contents.components(separatedBy: "\n")