Getting song name of streaming track in iTunes with Applescript - macos

How can I get the name of the song of current track in iTunes when this track is a radio?
I mean the string(s) that appears right below the radio name :)
Quering its name like below gives me the name of the radio (Trance Channel - DIGGITALLY IMPORTED - we can't define it!)but not the song
tell application "iTunes"
set thisTrack to current track
set trackName to the name of thisTrack
set trackTime to thisTrack's time
end tell
which is expected since the info in my library is:
but is there a way to specially deal this streaming tracks? and get their info correctly like iTunes does in the first picture? I know current track is a radio because its time will be missing value and not of the form MM:SS if that helps a bit.
Is this possible?

I looked in the applescript dictionary for iTunes and searched for "stream"...
tell application "iTunes"
current stream title
end tell

It appears that, in iTunes 12.2, a whole variety of interesting things are going on.
current stream title returns missing value when requesting the name of a stream coming from "For You" (e.g. something not in your current music library). name of the current track doesn't exist. For example, I'm listening to "Alternative Gems: 1994" from "For You" right now (Yay- grad school days) and I can't get any information about what is playing. If I go to the album the track is playing from to play something else, missing value and error -1728 on name of current track too.
When listening to Beats 1 radio as per #ivan above, I also get missing value but for name of the current track I get "Beats 1". As #dougscripts points out, the stream title stuff varies all over the map.
Listening to a radio station created via a "For You" seems to give me the correct name of the current track.
So, in short, chaos.

Not all streamers will format the stream data the same, so results from the current stream title property may not be consistent.

Much more hacking and I finally have found a way to get the data directly out from iTunes using the SDK.
This method will check the currentTrack like normal, but when it's detected that the artist is missing (a common understanding when the track is being streamed), we fall down to getting the values from the LCD display using values provided by Accessibility.
#!/usr/bin/env osascript -l JavaScript
/* globals Application */
function main () {
var itunes = new Application('iTunes')
var currentTrack = itunes.currentTrack
var output = {
name: currentTrack.name(),
artist: currentTrack.artist(),
position: itunes.playerPosition()
}
if (currentTrack.artist() === '') {
var app = new Application('System Events')
var itunesProcess = app.applicationProcesses.byName('iTunes')
// Get the text values from the first scrollable area which is the LCD Display
var arr = itunesProcess.windows[0].scrollAreas[0].staticTexts
output.name = arr[0].name()
// Clean up the artist name, as it may contain the Show Name.
output.artist = arr[2].name().replace(' — ' + currentTrack.name(), '')
}
return JSON.stringify(output, null, 2)
}
main()
Example output:
{
"name": "Wild Smooth (Gundam Radar Rip)",
"position": "34:06",
"artist": "MUBLA"
}
Be sure to chmod +x this script.
Note, it requires the calling application to be added to Accessibility Privacy

Another option - given the inconsistency of the output - might be to playback in VLC and query that instead - AppleScript support is pretty limited but at least the source code is available so you know what you can query.
osascript -e 'tell application "VLC" to get name of current item'

After weeks of tearing my hair out trying to figure this out myself.
I have managed to find a very hacky solution to this exact issue.
your not going to like it though, be warned.
In order to get the current playing track even if the audio is being streamed from Beats radio, you're going to have to consider a OCR approach (taking a screencapture, converting the image to text)
The following Ruby code will get you up-and-running with this solution.
It will check the current track, and if the artist field is blank, (which is the case when a track is streamed) then it fall's to the OCR method.
require 'json'
require 'rtesseract'
class CurrentTrack
def self.check
js_command = %Q{var itunes = Application("iTunes");
var currentTrack = itunes.currentTrack;
JSON.stringify({
window_bounds: itunes.windows[0].bounds(),
name: currentTrack.name(),
artist: currentTrack.artist(),
position: itunes.playerPosition()
})
}
command = "osascript -l JavaScript -e '#{js_command}'"
result = `#{command}`
json = JSON.parse(result, symbolize_names: true)
json[:position] = json[:position].to_i
json[:cue] = Time.at(json[:position]).utc.strftime('%H:%M:%S')
if json[:artist] == ''
sc_command = %Q{screencapture -R #{json[:window_bounds][:x]},#{json[:window_bounds][:y].to_i + 30},#{json[:window_bounds][:width]},#{json[:window_bounds][:height]} capture.png}
`#{sc_command}`
image = RTesseract.new("capture.png", processor: 'none')
ocr = image.to_s.split("\n") # Getting the value
unless ocr.first == 'Soulection'
json[:name] = ocr.first
json[:artist] = ocr[1].split(' — ').first
end
end
json.delete :window_bounds
json
end
end
You'll need to install rtesseract to get this working.
Caveats, this script requires the iTunes mini-player window to be visible somewhere on your desktop.

Related

AppleScript - Get complete information of current iTunes track

I'm trying to return the information of the current iTunes Track, which is playing on my computer.
I can get single informations like this:
tell application "iTunes"
get name of current track
end tell
This returns Thunderstruck for example.
But is it possible that I can get more information of the current track so it returns something like this:
"AC/DC, Thunderstruck, Iron Man 2"
Is this possible? Or do I have to create multiple scripts?
Thanks
To get name, artist and album
tell application "iTunes"
get {name, artist, album} of current track
end tell
The result is a list
To get everything
tell application "iTunes"
get properties of current track
end tell
The result is a record

iTunes AppleScript: query all playlists that contain a certain track

Working on a complex AppleScript for iTunes. One task is to accumulate a list of all playlists which contain a given track. I have this track object from somewhere else (a selection or whatever).
Currently, I've got a snippet something like this:
on containingPlaylists(theTrack)
tell application "iTunes"
set librarySource to the source named "Library"
set candidateLists to every user playlist in librarySource
set candidateId to (get id of theTrack)
set matchLists to {}
repeat with candidateList in candidateLists
set matchTracks to (file tracks in candidateList whose id = candidateId)
if (count of matchTracks) > 0 then
copy candidateList to end of matchLists
end if
end repeat
return matchLists
end tell
end containingPlaylists
This works but requires one Apple Event per playlist in the loop, which is expensive (perf) and throws away the intermediate results. What I'd RATHER do is something all in one query:
set matchLists to every playlist in librarySource whose file tracks contain theTrack
But this of course doesn't work (the particular error is "Handler only handles single objects." but not sure if that's insightful). I'm really just not sure if the language/app supports a query like this.
Can anyone confirm/deny/offer any insight? Thanks!
You can use this (work on iTunes 11 and 12):
tell application "iTunes"
set theTrack to item 1 of (get selection)
return user playlists of theTrack
end tell
Updated --
In the AppleScript dictionary:
artwork n [inh. item] : a piece of art within a track |
elements : contained by tracks. So artworks of thisTrack works
track n [inh. item] : playable audio source |
elements : contains artworks; contained by playlists. So playlists of thisTrack works, you can use user playlists of thisTrack
In iTunes.h (ObjC scripting bridge):
#interface iTunesTrack : iTunesItem
- (SBElementArray *) artworks;
it's not possible because playlists is not in the SBElementArray's list.
But I do not know why there is a difference between the AppleScript dictionary and the iTunes.h file.
I too wish a whose clause like that could be used. But alas. Someone else might come up with a better plan, but I'm pretty sure this is how I would find the playlists containing the selected track (it may be the most efficient):
set persisID to persistent ID of selection
set pp to playlists
set playListsWithIt to {}
repeat with p in pp
set tt to (tracks of p whose persistent ID is persisID)
if tt ≠ {} then set playListsWithIt to (playListsWithIt & (id of p))
end repeat
Then I can use those IDs for the next step. This includes, of course, playlists like "Recently Added", which may or may not be what you want; you'd have to put another step in there to 'filter' out such a result.

Bizarre field switch between cli and file output in Ruby

I'm having such an strange issue with an ruby script which i'm working with... in this script i parse an iTunes Library xml file and form objects for Artists, Albums and Tracks. In my Album class, i have two numeric field, YEAR and TRACK_COUNT.
My script parses correctly the two fields, let's say, for example, the output of it:
#<Album:0x007f59b1472a18 #compilation=false, #title="Straight Out Of Hell", #year=2013, #track_count=13, #trackList=[], #coverList=[]>
when i output this same object to file, it get crippled, transforming to this, here in json format:
{"compilation":false,"title":"Straight Out Of Hell","year":13,"track_count":13,"trackList":[],"coverList":[]}]
as you can see, the field YEAR get overwritten with the value in TRACK_COUNT field... i'm getting crazy with this, as i don't do any change to this field between these outputs!
UPDATE
As asked by #Amadan...
http://pastebin.com/1FUuvaCr Biblioteca.xml (EXCERPT)
http://pastebin.com/F8wgu6bz Track.rb
http://pastebin.com/3qhd4TRU Song.rb
http://pastebin.com/RNf5S7AZ dependencies.rb
http://pastebin.com/haXPpJgN Cover.rb
http://pastebin.com/1JYtT1nn Artist.rb
http://pastebin.com/qsgLsAJa Album.rb
http://pastebin.com/eiUAMfwR app.rb (MAIN SCRIPT)
This is happening because your source file is not as clean as you believe it to be. In some albums in the source XML, "Track Count" and "Year" are appearing on the same line, without a recognized line break between them. So you might have a line like this:
<key>Track Count</key><integer>12</integer><key>Year</key><integer>2006</integer>
When your if-else-if ladder asks if "track count" appears in the line, it does, so you're grabbing the first <integer>something</integer> match on the line. This works fine. But when you try to extract the year out of this line, you're again asking for the first <integer> on the line, which is the Track Count.
The bigger problem is that you're attempting to parse an XML file line-by-line, and that's not how they're meant to be read. Install the nokogiri gem and call this:
data = Nokogiri::XML('Biblioteca.xml')
Now you can get to any information contained in the document. The official tutorials on user Nokogiri are here: http://www.nokogiri.org/tutorials/
Use this method to parse your file:
def parse filename
xml = Nokogiri::XML(filename)
songs = xml.css('dict key').select{|key| key.text =~ /^[0-9]{4}$/}
songs.map do |song|
info = {}
song.next_element.css('key').each do |attribute|
info[attribute.text] = attribute.next_element.text
end
info
end
end
This will create a list of song hashes. Here are some examples for how to use it:
# load the two songs in your example file
songs = parse('Biblioteca.xml')
# Get the year of the first song
songs[0]['Year'] #=> 2006
# Get the Track Count of the second song's album
songs[1]['Track Count'] #=> 12
# Get the Name of the second song
songs[1]['Name'] #=> 'Baby Come On'
# Get the Album name of the second song
songs[1]['Album'] #=> 'When Your Heart Stops Beating'
From here, you can easily put info into your song objects. Let me know if you have any more questions.
I've found a library for iTunes dodgy plist xml standart... Nokogiri-plist... working fine now :D

iTunes -- making a script for copying album play counts

I'm trying to write a script which would let me copy the playcounts of one version of an album to another based on the title of the tracks.
Basically, scenario would be that I have the album on my computer along with all the playcounts. Then I rerip the original CD with higher quality (previously my quality preference was very low).
Now I want to automatically copy the playcounts of my old crappy quality rips to the new high quality ones.
I adopted a script from Doug Adams to try to do this, but when I try to run it it just gives me "A descriptor type mismatch occurred." without any indication as to the line where the problem is.
I've never used Apple Script before; does anybody know where the problem could be?
global thismany
tell application "iTunes"
if selection is not {} then
set sel to selection
repeat with t in sel
set t to contents of t
if class of t is file track or class of t is URL track then
if played count of t is 0 then
set thismany to 0
repeat with t2 in sel
set t2 to contents of t2
if class of t2 is file track or class of t2 is URL track then
if title of t is equal to title of t2 and t2 is not t then
set thismany to played count of t2
exit repeat
end if
end if
end repeat
set played count of t to (thismany as integer)
if (thismany as integer) is 0 then
try
set played date of t to missing value
end try
end if
end if
end if
end repeat
else -- no track selected
tell me to message_and_cancel("No tracks selected.")
end if
end tell
I figured this out, turns out the problem was that there is no such thing as a title of a track, the property is called the name of the track.
If anybody is interested, the full script can now be found here.

What is the best way to get keyboard events (input without press 'enter') in a Ruby console application?

I've been looking for this answer in the internet for a while and have found other people asking the same thing, even here. So this post will be a presentation of my case and a response to the "solutions" that I have found.
I am such new in Ruby, but for learning purposes I decided to create a gem, here.
I am trying to implement a keyboard navigation to this program, that will allow the user use short-cuts to select what kind of request he want to see. And in the future, arrow navigations, etc.
My problem: I can't find a consistent way to get the keyboard events from the user's console with Ruby.
Solutions that I have tried:
Highline gem: Seems do not support this feature anymore. Anyway it uses the STDIN, keep reading.
STDIN.getch: I need to run it in a parallel loop, because at the same time that the user can use a short-cut, more data can be created and the program needs to show it. And well, I display formated text in the console, (Rails log). When this loop is running, my text lost the all the format.
Curses: Cool but I need to set position(x,y) to display my text every time? It will get confusing.
Here is where I am trying to do it.
You may note that I am using "stty -raw echo" (turns raw off) before show my text and "stty raw -echo" (turns raw on) after. That keeps my text formated.
But my key listener loop is not working. I mean, It works in sometimes but is not consistent. If a press a key twice it don't work anymore and sometimes it stops alone too.
Let me put one part of the code here:
def run
# Two loops run in parallel using Threads.
# stream_log loops like a normal stream in the file, but it also parser the text.
# break it into requests and store in #requests_queue.
# stream_parsed_log stream inside the #requests_queue and shows it in the screen.
#requests_queue = Queue.new
#all_requests = Array.new
# It's not working yet.
Thread.new { listen_keyboard }
Thread.new { stream_log }
stream_parsed_log
end
def listen_keyboard
# not finished
loop do
char = STDIN.getch
case char
when 'q'
puts "Exiting."
exit
when 'a'
#types_to_show = ['GET', 'POST', 'PUT', 'DELETE', 'ASSET']
requests_to_show = filter_to_show(#all_requests)
command = true
when 'p'
#types_to_show = ['POST']
requests_to_show = filter_to_show(#all_requests)
command = true
end
clear_screen if command
#requests_queue += requests_to_show if command
command = false
end
end
I need a light in my path, what should I do?
That one was my mistake.
It's just a logic error in another part of code that was running in another thread so the ruby don't shows the error by default. I used ruby -d and realized what was wrong. This mistake was messing my keyboard input.
So now it's fixed and I am using STDIN.getch with no problem.
I just turn the raw mode off before show any string. And everything is ok.
You can check here, or in the gem itself.
That's it.

Resources