Hi,
I want to get the duration of all selected audio files in Finder. So I written an applescript like so
-- Zwraca długość podanego pliku w sekundach.
on pobierzDlugosc(sciezka)
set sekundy to 0
try
set informacjeOPliku to do shell script "/usr/bin/afinfo -x " & quoted form of POSIX path of sciezka
tell application "System Events"
set plist to make new property list item with data informacjeOPliku
set audio to property list item "audio_file" of property list item 1 of plist
set sciezki to property list item "tracks" of property list item 1 of audio
set sciezka to property list item "track" of property list item 1 of sciezki
set sekundy to property list item "duration" of property list item 1 of sciezka
return sekundy as number
end tell
on error w
tell application "VoiceOver" to output w
error number -128
end try
end pobierzDlugosc
-- Pobiera zaznaczenie pliku
tell application "Finder"
set zaznaczenie to selection as list
end tell
if length of zaznaczenie is 0 then tell application "VoiceOver" to output "Brak zaznaczenia"
set calkowitaDlugosc to 0
repeat with plik in zaznaczenie
set sciezkaDoPliku to (plik as alias)
set dlugoscPliku to pobierzDlugosc(sciezkaDoPliku)
set calkowitaDlugosc to calkowitaDlugosc + dlugoscPliku
end repeat
set godziny to calkowitaDlugosc div (60 * 60)
set minuty to (calkowitaDlugosc mod (60 * 60)) div 60
set sekundy to calkowitaDlugosc mod 60
But it gives me a system events error. Example output of afinfo when run straight from the terminal
The main issue with your pobierzDlugosc handler is that it is mixing Property List with XML text. System Events has both Property List and XML suites, and while similar, they are not quite the same.
To use the XML from the shell script, your handler can be modified, for example:
tell application "System Events"
set XMLdata to make new XML data with properties {text:informacjeOPliku}
set baseElement to first XML element of XMLdata
set audio to XML element "audio_file" of baseElement
set sciezki to XML element "tracks" of audio
set sciezka to XML element "track" of sciezki
set sekundy to XML element "duration" of sciezka
return (value of sekundy) as number
end tell
You can also use a more general-purpose handler to get the value or names of an XML element, for example:
# Get the contents of an XML element from simple XML data for a tag hierarchy list.
# The optional names boolean argument can be used to get list tag names instead of values.
to getElementContent from XMLdata for hierarchy as list given names:names as boolean : false
tell application "System Events" to try
if class of XMLdata is not XML data then error "not XML data"
set element to first XML element of XMLdata -- start at the base element
repeat with tagItem in hierarchy -- traverse tag hierarchy
set element to (get XML element tagItem of element)
end repeat
tell (get value of element) to if it is not missing value then return it -- element content
return item ((names as integer) + 1) of {value, name} of XML elements of element -- contained elements
on error errorMessage number errorNumber
error "getElementContent error: " & errorMessage & " (" & errorNumber & ")"
end try
end getElementContent
Where it is called by using a list of element tag names, for example:
tell application "System Events"
set XMLdata to make new XML data with properties {text:informacjeOPliku}
my (getElementContent from XMLdata for {"audio_file", "tracks", "track", "duration"})
end tell
An easier way would be to use Spotlight to get the duration directly, instead of creating an XML file and trying to get System Events to parse it. For example:
tell application "Finder" to repeat with anItem in (get selection as alias list)
tell me to set duration to (do shell script "/usr/bin/mdls -name kMDItemDurationSeconds -raw " & quoted form of POSIX path of anItem)
if duration is not "(null)" then set duration to my formatSeconds(duration)
log "The duration of '" & name of anItem & "' is " & duration -- or whatever
end repeat
to formatSeconds(theSeconds as integer) -- hh:mm:ss (wraps at 24 hours)
if theSeconds < 0 then set theSeconds to 0
tell "000000" & (10000 * (theSeconds mod days div hours) + 100 * (theSeconds mod hours div minutes) + (theSeconds mod minutes)) to return (text -6 thru -5) & ":" & (text -4 thru -3) & ":" & (text -2 thru -1)
end formatSeconds
FWIW, a somewhat different approach that uses the afinfo utility:
use framework "Foundation"
use scripting additions
tell application "Finder" to set theFiles to selection as alias list
if theFiles is {} then display dialog "One or more files must be selected in a Finder window" buttons {"OK"} cancel button 1 default button 1
set text item delimiters to {"/"}
set theSummary to ""
set totalDuration to 0
repeat with aFile in theFiles
set aFile to POSIX path of aFile
set fileName to text item -1 of aFile
try
set theInfo to do shell script "/usr/bin/afinfo -x " & quoted form of aFile
on error
display dialog quote & fileName & quote & " could not be processed" buttons {"OK"} cancel button 1 default button 1
end try
set durationSeconds to getSeconds(theInfo)
set durationMinutes to getMinutes(durationSeconds)
set theSummary to theSummary & (text item -1 of aFile) & " - " & durationMinutes & linefeed
set totalDuration to totalDuration + durationSeconds
end repeat
set totalDuration to getMinutes(totalDuration)
set theSummary to theSummary & "Total - " & totalDuration
set text item delimiters to {""}
display dialog "The names of selected files and their durations are:" & linefeed & linefeed & theSummary buttons {"OK"} default button 1
on getSeconds(theString)
set theString to current application's NSMutableString's stringWithString:theString
set thePattern to "(?is).*?<duration.*?(\\d+)\\..*" -- needs improvement
set matchesFound to (theString's replaceOccurrencesOfString:thePattern withString:"$1" options:1024 range:{0, theString's |length|()}) -- set to 0 if match not found
return theString as text as integer
end getSeconds
on getMinutes(theSeconds) -- thanks red_menace
if theSeconds < 0 then set theSeconds to 0
tell "000000" & (10000 * (theSeconds mod days div hours) + 100 * (theSeconds mod hours div minutes) + (theSeconds mod minutes)) to return (text -6 thru -5) & ":" & (text -4 thru -3) & ":" & (text -2 thru -1)
end getMinutes
Hi,
I continued with this approach. Now I have a weird error, its in Polish but the main issue is that access is supposedly denied.
-- Zwraca długość podanego pliku w sekundach.
on getLength(path)
try
set fileInfo to do shell script "/usr/bin/afinfo -x " & quoted form of POSIX path of path
tell application "System Events"
set xml to make new XML data with properties {text:fileInfo}
set baseElement to first XML element of xml
set audio to XML element "audio_file" of baseElement
set tracks to XML element "tracks" of audio
set track to XML element "track" of tracks
set durationElement to XML element "duration" of track
set seconds to value of durationElement
return (seconds as number)
end tell
on error w
tell application "VoiceOver" to output w
error number -128
end try
end getLength
-- Pobiera zaznaczenie pliku
tell application "Finder"
set sel to selection as list
end tell
if length of sel is 0 then
tell application "VoiceOver" to output "Brak zaznaczenia"
error -128
end if
set totalLength to 0
repeat with f in sel
set filePath to f as alias
set fileLength to getLength(filePath)
set totalLength to totalLength + fileLength
end repeat
set hours to totalLength div (60 * 60)
set minutes to (totalLength mod (60 * 60)) div 60
set seconds to totalLength mod 60
tell application "VoiceOver"
end tell
And here’s the error
Syntax error. Cannot set a value of seconds to«class valL» of durationElement. access denied
AppleScript is complaining because the term seconds is a class name (note the different formatting color in the editor) - just choose a different name for your variable, such as in your original post.
I tried to find where the class was documented, but about the only place I found was for the with timeout statement, and even that wasn’t very clear. Without color cues, I can see where that might be an issue. The Xcode UI is even worse, unfortunately.