├── .gitignore ├── applet.icns ├── TuneOut.scptd └── Contents │ ├── Resources │ ├── clear.png │ ├── Scripts │ │ └── main.scpt │ └── description.rtfd │ │ └── TXT.rtf │ ├── _CodeSignature │ ├── CodeDirectory │ ├── CodeSignature │ ├── CodeRequirements │ ├── CodeRequirements-1 │ └── CodeResources │ └── Info.plist ├── README.md └── TuneOut.applescript /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .DS_Store 3 | TuneOut.app 4 | -------------------------------------------------------------------------------- /applet.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purrloftruth/tuneout/HEAD/applet.icns -------------------------------------------------------------------------------- /TuneOut.scptd/Contents/Resources/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purrloftruth/tuneout/HEAD/TuneOut.scptd/Contents/Resources/clear.png -------------------------------------------------------------------------------- /TuneOut.scptd/Contents/Resources/Scripts/main.scpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purrloftruth/tuneout/HEAD/TuneOut.scptd/Contents/Resources/Scripts/main.scpt -------------------------------------------------------------------------------- /TuneOut.scptd/Contents/_CodeSignature/CodeDirectory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purrloftruth/tuneout/HEAD/TuneOut.scptd/Contents/_CodeSignature/CodeDirectory -------------------------------------------------------------------------------- /TuneOut.scptd/Contents/_CodeSignature/CodeSignature: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purrloftruth/tuneout/HEAD/TuneOut.scptd/Contents/_CodeSignature/CodeSignature -------------------------------------------------------------------------------- /TuneOut.scptd/Contents/_CodeSignature/CodeRequirements: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purrloftruth/tuneout/HEAD/TuneOut.scptd/Contents/_CodeSignature/CodeRequirements -------------------------------------------------------------------------------- /TuneOut.scptd/Contents/_CodeSignature/CodeRequirements-1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purrloftruth/tuneout/HEAD/TuneOut.scptd/Contents/_CodeSignature/CodeRequirements-1 -------------------------------------------------------------------------------- /TuneOut.scptd/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleAllowMixedLocalizations 6 | 7 | CFBundleDevelopmentRegion 8 | English 9 | CFBundleIdentifier 10 | org.purrl.TuneOut 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | TuneOut 15 | CFBundleShortVersionString 16 | 0.9-alpha 17 | LSMinimumSystemVersionByArchitecture 18 | 19 | x86_64 20 | 10.6 21 | 22 | LSRequiresCarbon 23 | 24 | WindowState 25 | 26 | bundleDividerCollapsed 27 | 28 | bundlePositionOfDivider 29 | 783 30 | dividerCollapsed 31 | 32 | eventLogLevel 33 | 0 34 | name 35 | ScriptWindowState 36 | positionOfDivider 37 | 420 38 | savedFrame 39 | 33 44 1082 834 0 0 1440 878 40 | selectedTab 41 | log 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /TuneOut.scptd/Contents/Resources/description.rtfd/TXT.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf600 2 | {\fonttbl\f0\fnil\fcharset0 Verdana;} 3 | {\colortbl;\red255\green255\blue255;\red76\green78\blue78;} 4 | {\*\expandedcolortbl;;\csgenericrgb\c29999\c30457\c30457;} 5 | \vieww10800\viewh8400\viewkind0 6 | \deftab720 7 | \pard\tx577\tx1155\tx1733\tx2311\tx2889\tx3467\tx4045\tx4623\tx5201\tx5779\tx6357\tx6935\tx7513\tx8091\tx8669\tx9247\tx9825\tx10403\tx10981\tx11559\tx12137\tx12715\tx13293\tx13871\tx14449\tx15027\tx15605\tx16183\tx16761\tx17339\tx17917\tx18495\tx19072\tx19650\tx20228\tx20806\tx21384\tx21962\tx22540\tx23118\tx23696\tx24274\tx24852\tx25430\tx26008\tx26586\tx27164\tx27742\tx28320\tx28898\tx29476\tx30054\tx30632\tx31210\tx31788\tx32366\tx32944\tx33522\tx34100\tx34678\tx35256\tx35834\tx36412\tx36990\tx37567\tx38145\tx38723\tx39301\tx39879\tx40457\tx41035\tx41613\tx42191\tx42769\tx43347\tx43925\tx44503\tx45081\tx45659\tx46237\tx46815\tx47393\tx47971\tx48549\tx49127\tx49705\tx50283\tx50861\tx51439\tx52017\tx52595\tx53173\tx53751\tx54329\tx54907\tx55485\tx56062\tx56640\tx57218\tx57796\pardeftab720\partightenfactor0 8 | 9 | \f0\fs24 \cf2 \CocoaLigature0 TuneOut is an AppleScript application that saves the currently playing iTunes, Spotify, Nightbot or Moobot track's information to a file (~/Library/Application Support/TuneOut/np.txt, specifically). This might be useful if you use an application like OBS to stream live video and want to incorporate your currently playing song on the display.\ 10 | \pard\tx577\tx1155\tx1733\tx2311\tx2889\tx3467\tx4045\tx4623\tx5201\tx5779\tx6357\tx6935\tx7513\tx8091\tx8669\tx9247\tx9825\tx10403\tx10981\tx11559\tx12137\tx12715\tx13293\tx13871\tx14449\tx15027\tx15605\tx16183\tx16761\tx17339\tx17917\tx18495\tx19072\tx19650\tx20228\tx20806\tx21384\tx21962\tx22540\tx23118\tx23696\tx24274\tx24852\tx25430\tx26008\tx26586\tx27164\tx27742\tx28320\tx28898\tx29476\tx30054\tx30632\tx31210\tx31788\tx32366\tx32944\tx33522\tx34100\tx34678\tx35256\tx35834\tx36412\tx36990\tx37567\tx38145\tx38723\tx39301\tx39879\tx40457\tx41035\tx41613\tx42191\tx42769\tx43347\tx43925\tx44503\tx45081\tx45659\tx46237\tx46815\tx47393\tx47971\tx48549\tx49127\tx49705\tx50283\tx50861\tx51439\tx52017\tx52595\tx53173\tx53751\tx54329\tx54907\tx55485\tx56062\tx56640\tx57218\tx57796\pardeftab720\li785\fi-786\pardirnatural\partightenfactor0 11 | \cf2 \ 12 | Copyright \'a9 2016, 2019 Ruth Harris.\ 13 | \ 14 | Permission is hereby granted, free of charge, to any person obtaining a copy\ 15 | of this software and associated documentation files (the "Software"), to deal\ 16 | in the Software without restriction, including without limitation the rights\ 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ 18 | copies of the Software, and to permit persons to whom the Software is\ 19 | furnished to do so, subject to the following conditions:\ 20 | \ 21 | The above copyright notice and this permission notice shall be included in all\ 22 | copies or substantial portions of the Software.\ 23 | \ 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\ 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\ 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\ 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\ 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\ 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\ 30 | SOFTWARE.} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # _This is old, unmaintained software which should not be used in a modern production context._ 2 | 3 | # TuneOut 4 | 5 | TuneOut is an AppleScript application that saves the currently playing iTunes, Spotify, Nightbot or Moobot track's information to a file (~/Library/Application Support/TuneOut/np.txt, specifically). This might be useful if you use an application like OBS to stream live video and want to incorporate your currently playing song on the display. 6 | 7 | ## How to use 8 | 9 | 1. Download the latest version of TuneOut.app from the project's [releases](http://github.com/wonaldson/tuneout/releases) page and unzip the file. 10 | 2. Double-click the .app file. (Depending on your Gatekeeper settings, you may have to right-click the .app file and click "Open".) 11 | 3. Point your OBS text-as-file location to `~/Library/Application Support/TuneOut/np.txt`, where `~` consists of your User folder (usually `/Users/{your user name here}`). 12 | 4. For iTunes cover art support, point your OBS image location to `~/Library/Application Support/TuneOut/art.png`. 13 | 5. There is no step five! 14 | 15 | ## How to build 16 | 17 | 1. Clone the repository to your macOS computer. 18 | 2. In `build.command`, replace the code signing identity with your own. 19 | 3. In the repository directory, make `build.command` executable: `chmod +x build.command` 20 | 4. Double-click `build.command` in Finder or run it in the terminal: `./build.command` 21 | 22 | TuneOut.app will be built in `./build/` (where `.` is the repository folder) and a .zip file with the version number will be created. 23 | 24 | ## Caveats 25 | 26 | If you play songs with non-latin characters, make sure your font supports them. The font renderer used by OBS does not fall back to fonts where the glyphs needed appears like macOS font rendering typically does. 27 | 28 | Support for web players such as Nightbot and Moobot only functions when running in Safari or Google Chrome. 29 | 30 | To enable Safari support, you must enable "Allow JavaScript from Apple Events" in Safari's Develop menu. If you do not have a Develop menu, it can be enabled in the Advanced section of Safari's Preferences. 31 | 32 | ## Thanks 33 | 34 | To [dzomb](https://github.com/dzomb) who wrote the [original version](https://github.com/dzomb/tuneout) of this script and to [wildgift](http://twitch.tv/wildgifticus) for getting me to write this. 35 | 36 | Additional thanks to [Christopher T. Howlett](http://howlettstudios.com) for the royalty-free tuning fork shape used in our icon. 37 | 38 | ## License 39 | 40 | TuneOut is licensed under the MIT license. 41 | 42 | Copyright © 2016, 2019 Ruth Harris. 43 | 44 | Permission is hereby granted, free of charge, to any person obtaining a copy 45 | of this software and associated documentation files (the "Software"), to deal 46 | in the Software without restriction, including without limitation the rights 47 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 48 | copies of the Software, and to permit persons to whom the Software is 49 | furnished to do so, subject to the following conditions: 50 | 51 | The above copyright notice and this permission notice shall be included in all 52 | copies or substantial portions of the Software. 53 | 54 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 55 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 56 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 57 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 58 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 59 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 60 | SOFTWARE. 61 | -------------------------------------------------------------------------------- /TuneOut.scptd/Contents/_CodeSignature/CodeResources: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | files 6 | 7 | Resources/Scripts/main.scpt 8 | 9 | 4gA9RX9soWC/MlRUUkGEDu9wCUw= 10 | 11 | Resources/clear.png 12 | 13 | m+UVI9rvGvfLC7nvM7JcKMQ4aYU= 14 | 15 | Resources/description.rtfd/TXT.rtf 16 | 17 | RSqAMCQ01ANvneZBRmqyh3nrpDE= 18 | 19 | 20 | files2 21 | 22 | Resources/Scripts/main.scpt 23 | 24 | hash 25 | 26 | 4gA9RX9soWC/MlRUUkGEDu9wCUw= 27 | 28 | hash2 29 | 30 | wKpqjRNLiAEkKDEC+JgVKKgH4sEaOjqW0gSxAPp1IqI= 31 | 32 | 33 | Resources/clear.png 34 | 35 | hash 36 | 37 | m+UVI9rvGvfLC7nvM7JcKMQ4aYU= 38 | 39 | hash2 40 | 41 | Xnbscatt7CrsjvS1EsMdCMjVaIYZAP5DkratmUCRePE= 42 | 43 | 44 | Resources/description.rtfd/TXT.rtf 45 | 46 | hash 47 | 48 | RSqAMCQ01ANvneZBRmqyh3nrpDE= 49 | 50 | hash2 51 | 52 | 9YB/nPVm0BiMsOcQByz7AMlZifvvQNty/u84KKkm7L4= 53 | 54 | 55 | 56 | rules 57 | 58 | ^Resources/ 59 | 60 | ^Resources/.*\.lproj/ 61 | 62 | optional 63 | 64 | weight 65 | 1000 66 | 67 | ^Resources/.*\.lproj/locversion.plist$ 68 | 69 | omit 70 | 71 | weight 72 | 1100 73 | 74 | ^Resources/Base\.lproj/ 75 | 76 | weight 77 | 1010 78 | 79 | ^version.plist$ 80 | 81 | 82 | rules2 83 | 84 | .*\.dSYM($|/) 85 | 86 | weight 87 | 11 88 | 89 | ^(.*/)?\.DS_Store$ 90 | 91 | omit 92 | 93 | weight 94 | 2000 95 | 96 | ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ 97 | 98 | nested 99 | 100 | weight 101 | 10 102 | 103 | ^.* 104 | 105 | ^Info\.plist$ 106 | 107 | omit 108 | 109 | weight 110 | 20 111 | 112 | ^PkgInfo$ 113 | 114 | omit 115 | 116 | weight 117 | 20 118 | 119 | ^Resources/ 120 | 121 | weight 122 | 20 123 | 124 | ^Resources/.*\.lproj/ 125 | 126 | optional 127 | 128 | weight 129 | 1000 130 | 131 | ^Resources/.*\.lproj/locversion.plist$ 132 | 133 | omit 134 | 135 | weight 136 | 1100 137 | 138 | ^Resources/Base\.lproj/ 139 | 140 | weight 141 | 1010 142 | 143 | ^[^/]+$ 144 | 145 | nested 146 | 147 | weight 148 | 10 149 | 150 | ^embedded\.provisionprofile$ 151 | 152 | weight 153 | 20 154 | 155 | ^version\.plist$ 156 | 157 | weight 158 | 20 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /TuneOut.applescript: -------------------------------------------------------------------------------- 1 | (* 2 | 3 | Copyright © 2016, 2019 Ruth Harris. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | *) 24 | 25 | (* Thanks *) 26 | # Thanks to dzomb who wrote the original version of this script. 27 | # You can find that here: https://github.com/dzomb/tuneout 28 | 29 | 30 | (* Editing below this line may break things. *) 31 | 32 | (* thanks to http://www.macosxautomation.com/applescript/sbrt/sbrt-09.html *) 33 | (* thanks to http://macscripter.net/viewtopic.php?id=24534 for unicode help *) 34 | on write_to_file(this_data, target_file, append_data) 35 | try 36 | set the target_file to the target_file as string 37 | set the open_target_file to open for access file target_file with write permission 38 | if append_data is false then set eof of the open_target_file to 0 39 | write this_data to the open_target_file as Çclass utf8È starting at eof 40 | close access the open_target_file 41 | return true 42 | on error 43 | try 44 | close access file target_file 45 | end try 46 | return false 47 | end try 48 | end write_to_file 49 | 50 | on binary_write_to_file(this_data, target_file, append_data) 51 | try 52 | set the target_file to the target_file as string 53 | set the open_target_file to open for access file target_file with write permission 54 | if append_data is false then set eof of the open_target_file to 0 55 | write this_data to the open_target_file starting at eof 56 | close access the open_target_file 57 | return true 58 | on error 59 | try 60 | close access file target_file 61 | end try 62 | return false 63 | end try 64 | end binary_write_to_file 65 | 66 | on debug(errorText, displayNotification) 67 | if my debugMode then 68 | log "DEBUG: " & errorText 69 | if displayNotification then display notification "DEBUG: " & errorText 70 | end if 71 | end debug 72 | 73 | on check_web_player(playerTitle, desiredPageTitle, determinePlayerStateCode, grabTrackCode, stoppedWhenNotPlaying) 74 | set rawTrack to null 75 | set rawArt to null 76 | 77 | if not my safariDisabled then 78 | (* Safari Support *) 79 | try 80 | if application "Safari" is running then 81 | tell application "Safari" 82 | set safariTab to null 83 | 84 | repeat with i in (windows whose its document is not missing value) 85 | if (count of (tabs of i)) is greater than 0 then 86 | repeat with j in (tabs of i) 87 | if name of j is desiredPageTitle then 88 | set safariTab to j 89 | exit repeat 90 | end if 91 | end repeat 92 | if safariTab is not null then 93 | exit repeat 94 | end if 95 | end if 96 | end repeat 97 | 98 | if safariTab is not null then 99 | tell safariTab 100 | set isPlaying to do JavaScript determinePlayerStateCode 101 | if isPlaying then 102 | set rawTrack to do JavaScript grabTrackCode 103 | else 104 | if not stoppedWhenNotPlaying then set rawTrack to "Paused" 105 | end if 106 | end tell 107 | end if 108 | 109 | end tell 110 | end if 111 | on error errStr number errorNumber 112 | debug("[SAFARI] " & desiredPageTitle & " had a booboo. " & errStr & space & errorNumber, true) 113 | set rawTrack to missing value 114 | set rawArt to missing value 115 | if my debugMode then error errStr number errorNumber 116 | end try 117 | end if 118 | if (rawTrack is null or rawTrack is missing value) and (rawArt is null or rawArt is missing value) then 119 | (* Google Chrome Support *) 120 | try 121 | if application "Google Chrome" is running then 122 | tell application "Google Chrome" 123 | set chromeTab to null 124 | 125 | repeat with i in (windows) 126 | if (count of (tabs of i)) is greater than 0 then 127 | repeat with j in (tabs of i) 128 | if title of j is desiredPageTitle then 129 | set chromeTab to j 130 | exit repeat 131 | end if 132 | end repeat 133 | if chromeTab is not null then 134 | exit repeat 135 | end if 136 | end if 137 | end repeat 138 | 139 | if chromeTab is not null then 140 | tell chromeTab 141 | set isPlaying to execute javascript determinePlayerStateCode 142 | if isPlaying then 143 | set rawTrack to execute javascript grabTrackCode 144 | else 145 | if not stoppedWhenNotPlaying then set rawTrack to "Paused" 146 | end if 147 | end tell 148 | end if 149 | 150 | end tell 151 | end if 152 | on error errStr number errorNumber 153 | debug("[CHROME] " & desiredPageTitle & " had a booboo. " & errStr & space & errorNumber, true) 154 | set rawTrack to missing value 155 | set rawArt to missing value 156 | if my debugMode then error errStr number errorNumber 157 | end try 158 | end if 159 | 160 | return {track:rawTrack, art:rawArt} 161 | end check_web_player 162 | 163 | on check_iTunes() 164 | set rawTrack to null 165 | set rawArt to null 166 | 167 | try 168 | if application "iTunes" is running then 169 | tell application "iTunes" 170 | if player state is not stopped then 171 | (* First, let's try and figure out the art situation *) 172 | 173 | try 174 | set artwk to first artwork of current track 175 | set rawArt to raw data of artwk 176 | end try 177 | 178 | (* Now we'll deal with track data *) 179 | 180 | if player state is paused then 181 | set rawTrack to "Paused" 182 | 183 | else 184 | if kind of current track is "Internet audio stream" then 185 | set rawTrack to current stream title 186 | else 187 | if artist of current track is "" then 188 | set rawTrack to name of current track 189 | else 190 | set rawTrack to artist of current track & " - " & name of current track 191 | end if 192 | end if 193 | end if 194 | end if 195 | end tell 196 | end if 197 | on error errStr number errorNumber 198 | debug("iTunes had a booboo. " & errStr & space & errorNumber, true) 199 | if my debugMode then error errStr number errorNumber 200 | return {track:missing value, art:missing value} 201 | end try 202 | 203 | return {track:rawTrack, art:rawArt} 204 | end check_iTunes 205 | 206 | on check_spotify() 207 | set rawTrack to null 208 | set rawArt to null 209 | 210 | try 211 | if application "Spotify" is running then 212 | tell application "Spotify" 213 | if player state is not stopped then 214 | (* First, let's try and figure out the art situation *) 215 | 216 | set spotifyArtUrl to artwork url of current track 217 | if spotifyArtUrl is not my spotifyArtUrlOld then 218 | (* If you can figure out a way to do this without saving it to the filesystem 219 | and also doesn't glitch out the art, please file a pull request. For some 220 | reason it's almost as if the script continues on without waiting for the 221 | file to fully download. *) 222 | 223 | set result to do shell script "curl " & spotifyArtUrl & " -o " & quoted form of POSIX path of my spotifyArtTempFullPathT 224 | set rawArt to read file (my spotifyArtTempFullPathT) 225 | 226 | set my spotifyArtUrlOld to spotifyArtUrl 227 | else 228 | set rawArt to my rawArtOld 229 | end if 230 | 231 | (* Now we'll deal with track data *) 232 | 233 | if player state is paused then 234 | set rawTrack to "Paused" 235 | 236 | else 237 | if artist of current track is "" then 238 | set rawTrack to name of current track 239 | else 240 | set rawTrack to artist of current track & " - " & name of current track 241 | end if 242 | end if 243 | end if 244 | end tell 245 | end if 246 | on error errStr number errorNumber 247 | debug("Spotify had a booboo. " & errStr & space & errorNumber, true) 248 | if my debugMode then error errStr number errorNumber 249 | return {track:missing value, art:missing value} 250 | end try 251 | 252 | return {track:rawTrack, art:rawArt} 253 | end check_spotify 254 | 255 | on check_playful_stream() 256 | set playerTitle to "Playful Stream" 257 | set desiredPageTitle to "Playful" 258 | set determinePlayerStateCode to "(document.getElementById('b-pause').style.visibility == 'visible');" 259 | set grabTrackCode to "document.getElementById('now-playing-data').textContent;" 260 | set stoppedWhenNotPlaying to false 261 | 262 | return check_web_player(playerTitle, desiredPageTitle, determinePlayerStateCode, grabTrackCode, stoppedWhenNotPlaying) 263 | end check_playful_stream 264 | 265 | on check_nightbot() 266 | set playerTitle to "Nightbot" 267 | set desiredPageTitle to "Nightbot - Song Requests" 268 | set determinePlayerStateCode to "document.getElementsByClassName('pause-play-container')[0].getElementsByClassName('fa-play')[0].classList.contains('ng-hide');" 269 | 270 | set grabTrackCode to "document.getElementsByClassName('current-track')[0].getElementsByTagName('h4')[0].textContent;" 271 | set stoppedWhenNotPlaying to false 272 | 273 | return check_web_player(playerTitle, desiredPageTitle, determinePlayerStateCode, grabTrackCode, stoppedWhenNotPlaying) 274 | end check_nightbot 275 | 276 | on check_moobot() 277 | set playerTitle to "Moobot" 278 | set desiredPageTitle to "Moobot, your Twitch chat moderator bot" 279 | set determinePlayerStateCode to "(document.getElementsByClassName('widget-songrequests')[0].getElementsByClassName('btn-play')[0].getElementsByClassName('icon-stop')[0] != null);" 280 | set grabTrackCode to " 281 | while (document.getElementById('songrequests-widget-info') == null) { 282 | document.getElementsByClassName('widget-songrequests')[0].getElementsByClassName('btn-info')[0].click(); 283 | } 284 | document.getElementById('songrequests-widget-info').getElementsByTagName('p')[0].textContent;" 285 | set stoppedWhenNotPlaying to true 286 | 287 | return check_web_player(playerTitle, desiredPageTitle, determinePlayerStateCode, grabTrackCode, stoppedWhenNotPlaying) 288 | end check_moobot 289 | 290 | on test_safari() 291 | try 292 | tell application "Safari" 293 | tell tab 1 of window 1 294 | do JavaScript "return true;" 295 | end tell 296 | end tell 297 | on error errStr number errNumber 298 | set result to button returned of (display dialog "You must enable the 'Allow JavaScript from Apple Events' option in Safari's Develop menu to use web-based players with Safari. 299 | 300 | Continue without Safari support? " with icon caution with title my appName) 301 | if result is "Cancel" then 302 | error number -128 303 | else 304 | set my safariDisabled to true 305 | end if 306 | end try 307 | end test_safari 308 | 309 | on run 310 | set appName to "TuneOut" 311 | set appVersion to "0.8" 312 | set debugMode to (name of current application is not appName) 313 | 314 | set applicationSupportPathP to path to application support from user domain as Unicode text 315 | set applicationSupportPath to applicationSupportPathP & appName & ":" 316 | set applicationSupportPathT to applicationSupportPath & "tmp:" 317 | 318 | tell application "Finder" 319 | if (exists applicationSupportPath) is false then make new folder at applicationSupportPathP with properties {name:appName} 320 | if (exists applicationSupportPathT) is false then make new folder at applicationSupportPath with properties {name:"tmp"} 321 | end tell 322 | 323 | set textFilename to "np.txt" 324 | set artFilename to "art.png" 325 | set artTempFilename to "art.tmp" 326 | set clearArtFilename to "clear.png" 327 | set spotifyArtTempFilename to "spotify.jpg" 328 | set logFilename to "debug.txt" (* Unused *) 329 | 330 | set textFullPath to applicationSupportPath & textFilename 331 | set textFullPathT to applicationSupportPathT & textFilename 332 | set artFullPath to applicationSupportPath & artFilename 333 | set artFullPathT to applicationSupportPathT & artFilename 334 | set artTempFullPath to applicationSupportPath & artTempFilename 335 | set artTempFullPathT to applicationSupportPathT & artTempFilename 336 | set spotifyArtTempFullPathT to applicationSupportPathT & spotifyArtTempFilename 337 | set logFullPath to applicationSupportPath & logFilename (* Unused *) 338 | 339 | set supportedPlayers to {"iTunes", "Spotify", "Playful Stream", "Nightbot", "Moobot"} 340 | 341 | set rawTrackOld to "" 342 | set rawArtOld to null 343 | set dataPlayersOld to {} 344 | set spotifyArtUrlOld to "" 345 | 346 | set safariDisabled to false 347 | 348 | repeat with i from 1 to count of supportedPlayers 349 | copy {track:null, art:null} to the end of dataPlayersOld 350 | end repeat 351 | 352 | set clearData to read (path to resource clearArtFilename) 353 | 354 | tell application "Image Events" 355 | launch 356 | end tell 357 | 358 | set operational to false 359 | 360 | try 361 | test_safari() 362 | on error errStr number errNumber 363 | if errNumber is -128 then 364 | if name of current application is appName then 365 | return 366 | quit 367 | else 368 | error number -128 369 | end if 370 | end if 371 | end try 372 | 373 | debug("Hello, I am " & appName & " (" & appVersion & ")", false) 374 | debug("Debugging is enabled.", false) 375 | 376 | set operational to true 377 | 378 | display notification appName & space & appVersion & " is now running. To quit, right-click the Dock icon and click \"Quit\"." sound name "Submarine" 379 | 380 | (* Script editor testing *) 381 | 382 | if name of current application is not appName then 383 | repeat while true 384 | set d to idle 385 | delay d 386 | end repeat 387 | end if 388 | end run 389 | 390 | on idle 391 | if my operational then 392 | set chosenData to {track:null, art:null} 393 | set dataPlayers to {} 394 | 395 | copy check_iTunes() to end of dataPlayers 396 | copy check_spotify() to end of dataPlayers 397 | copy check_playful_stream() to end of dataPlayers 398 | copy check_nightbot() to end of dataPlayers 399 | copy check_moobot() to end of dataPlayers 400 | 401 | debug(" 402 | Iteration for " & (current date), false) 403 | debug("iTunes: " & track of item 1 of dataPlayers, false) 404 | debug("Spotify: " & track of item 2 of dataPlayers, false) 405 | debug("Playful Stream: " & track of item 3 of dataPlayers, false) 406 | debug("Nightbot: " & track of item 4 of dataPlayers, false) 407 | debug("Moobot: " & track of item 5 of dataPlayers, false) 408 | 409 | repeat with i from 1 to count of dataPlayers 410 | if track of item i of dataPlayers is not null then 411 | if track of item i of dataPlayers is not missing value then 412 | set chosenData to item i of dataPlayers 413 | else 414 | debug("We had a missing value error. " & (current date), true) 415 | set chosenData to item i of my dataPlayersOld 416 | end if 417 | if track of chosenData is not null then exit repeat 418 | end if 419 | end repeat 420 | 421 | set rawTrack to track of chosenData 422 | set rawArt to art of chosenData 423 | 424 | (* If we don't have any data... *) 425 | if rawTrack is null then set rawTrack to "Stopped" 426 | if rawArt is null then set rawArt to my clearData 427 | 428 | (* Write images & text to temp files if they have changed *) 429 | if rawArt is not equal to my rawArtOld then 430 | my binary_write_to_file(rawArt, my artTempFullPathT, false) 431 | 432 | tell application "Image Events" 433 | set tempImage to open my artTempFullPathT 434 | scale tempImage to size 1000 435 | save tempImage as PNG in my artFullPathT 436 | close tempImage 437 | my debug("Art successfully saved.", false) 438 | end tell 439 | end if 440 | 441 | if rawTrack is not equal to my rawTrackOld then 442 | my write_to_file(rawTrack, my textFullPathT, false) 443 | debug("Track data changed. Writing: " & rawTrack, false) 444 | end if 445 | 446 | (* Move them at the same time to update simultaneously instead of staggered *) 447 | if rawTrack is not equal to my rawTrackOld then 448 | do shell script "mv " & quoted form of POSIX path of my textFullPathT & space & quoted form of POSIX path of my applicationSupportPath 449 | end if 450 | 451 | if rawArt is not equal to my rawArtOld then 452 | do shell script "mv " & quoted form of POSIX path of my artFullPathT & space & quoted form of POSIX path of my applicationSupportPath 453 | end if 454 | 455 | (* Finally, update old data listing *) 456 | if rawArt is not equal to my rawArtOld then 457 | set my rawArtOld to rawArt 458 | end if 459 | 460 | if rawTrack is not equal to my rawTrackOld then 461 | set my rawTrackOld to rawTrack 462 | end if 463 | 464 | set my dataPlayersOld to dataPlayers 465 | 466 | debug("We ended up with " & rawTrack, false) 467 | else 468 | quit 469 | end if 470 | 471 | return 0.5 472 | end idle 473 | 474 | on reopen 475 | display notification (my rawTrackOld) with title "Current track display" 476 | end reopen 477 | 478 | on quit 479 | try 480 | log "I think we're done here." 481 | write_to_file("Stopped", my textFullPath, false) 482 | binary_write_to_file(my clearData, my artFullPath, false) 483 | 484 | close access textReference 485 | close access artReference 486 | log "Successfully closed down." 487 | end try 488 | continue quit 489 | end quit 490 | 491 | 492 | --------------------------------------------------------------------------------