I’ve played around with as many XML libraries, methods in AppleScript and found
this the handiest. It can convert a xmlDoc, or XML String to a Dictionary (Record).
Which I find is much easier to the find the elements and attributes that I’m looking for
using NSArray’s valueForKeyPath methods
Or use Apple’s theXMLDoc’s nodesForXPath:… to get an array of xmlElements
then convert that to a XMLDoc or XMLString and then convert to a Dictionary.
I find it involves less code and is way easier to manage.
It’s simple enough to include the framework in your AppleScript.
return the value of the XML element named key in the trackdata
with this:
tell (a reference to XML element named key in the trackdata) to ¬
if it exists then
return its value
else
return false
end if
Then, when you do a call like this:
trackInfo given type:"Video", key:"blahblah"
(where the XML key “blah blah” clearly doesn’t exist), it will return false. From here, you can decide how you want to handle a false return value, e.g.
set info to trackInfo given type:"Video", key:"blahblah"
if the info = false then error "Incorrect Codec"
Thank you for all of your help. It’s starting to come together.
I have it working to check the attributes of a single file. I’m now trying to merge it back into the application script that can check files as a batch if needed.
It looks like the issue I’m running into is my MediaInfo handler/subroutine is not being called? The error I keep getting is that “PromoCodec” is not defined. (I’m guessing all of my variables are not populating?) Can I not use multiple Handlers in a script?
I’m also having trouble with these two particular lines:
set theNameSpaced to do shell script "basename " & quoted form of thisFile
and
set theName to do shell script ("echo " & theNameSpaced & " | tr -s ' '")
My understanding is the top line should give me the basefile name of the file the script is currently checking. I’m not exactly sure what the echo shell script is doing. This script was written by someone else and I’ve been asked to update it.
Here is the full script as it exists right now:
on open theseFiles
set theErrorCounter to 0
set theNotaNumber to 0
set theNotaNumberCounter to 0
set theNoS4M to 0
set theNoS4MCounter to 0
set counter to 0
set counterTime to 0
set theErrorFiles to 0
set theErrorList to ""
set theList to ""
set theTimeList to ""
set theNoS4MList to ""
set theTimeListNoCheck to ""
set theRightFormats to {"XDCAM HD422", "50.0 Mbps", "29.970 fps"} as list
repeat with thisFile in theseFiles
try
tell application "Finder"
set extension hidden of thisFile to false
end tell
set theName to name of (info for thisFile)
set thePath to the POSIX path of thisFile
set n to the number of characters in theName
set n to (n - 11)
set theRealName to (characters n through end of theName) as string
set theNewPath to quoted form of ("/Users/user/Desktop/Proxies" & theRealName)
--THE ABOVE LINE IS WHERE THE FILE WILL BE POSTED IF IT PASSES ALL TECH CHECKS
set theOldpath to quoted form of thePath
set theErrorCounter to 0
on error
set theErrorCounter to 1
end try
if theErrorCounter is equal to 0 then
try
--------------------------------
set theNameSpaced to do shell script "basename " & quoted form of thisFile
set tracks to the mediainfoFile()'s tracks
set info to trackInfo given type:"Video", key:"Commercial_name"
if the info = false then error "Incorrect Codec"
set PromoCodec to (trackInfo given type:"Video", key:"Commercial_name")
set PromoDuration to (trackInfo given type:"Audio", id:2, key:"Duration")
set theDuration to returnNumbersInString(PromoDuration)
set Duration to SAR(theDuration, ", ", ".")
set theNumber to the first word of theName
set theS4MNumber to the word -2 of theName
set theRealNameNumber to the first word of theRealName
set PromoFrameRate to (trackInfo given type:"Video", key:"Frame_rate")
set FrameRate to returnNumbersInString(PromoFrameRate)
set Frames to SAR(FrameRate, ".", "")
set theFPS to Frames
set theFPSround to round (theFPS / 100)
try
set theCalc to theNumber * theFPSround
on error
set theNotaNumber to 1
end try
if the number of characters of theS4MNumber is equal to 8 then
try
set theS4MCalc to (theS4MNumber + 2)
on error
set theNoS4M to 1
end try
else
set theNoS4M to 1
end if
if the number of characters of theRealNameNumber is not equal to 8 then
set theNoS4M to 1
end if
------------------------------
set theName to do shell script ("echo " & theNameSpaced & " | tr -s ' '")
set theErrorCounter to 0
on error
set theErrorCounter to 1
end try
end if
if theErrorCounter is equal to 0 then
tell application "Finder"
if theNoS4M is not equal to 0 then
set label index of thisFile to 4
set theNoS4MList to theNoS4MList & theName & "
"
set theNoS4MCounter to theNoS4MCounter + 1
set theNoS4M to 0
else
if PromoCodec is not in theRightFormats then
set label index of thisFile to 2
set counter to counter + 1
set theList to theList & theName & "
" & " • codec is " & PromoCodec & "
"
else
if PromoFrameRate is not in theRightFormats then
set label index of thisFile to 7
set theTimeListNoCheck to theTimeListNoCheck & theName & "
"
set theNotaNumberCounter to theNotaNumberCounter + 1
set theNotaNumber to 0
else
if theNotaNumber is equal to 0 then --and (theDuration is greater than (theCalc + 1) or theDuration is less than (theCalc - 1))
set label index of thisFile to 3
set counterTime to counterTime + 1
set theTimeList to theTimeList & theName & "
" & " • length is " & theDuration
else
set label index of thisFile to 6
do shell script "cp " & theOldpath & " " & theNewPath
end if
end if
end if
end if
end tell
set theErrorCounter to 0
end if
if theErrorCounter is not equal to 0 then
tell application "Finder"
set label index of thisFile to 5
set theErrorFiles to theErrorFiles + 1
set theErrorList to theErrorList & (name of (info for thisFile)) & "
"
end tell
end if
end repeat
tell application "Finder"
if theNoS4MList is not equal to "" then
display dialog "..." & theNoS4MCounter & " file(s) do not have a proper S4M number...
" & theNoS4MList & "
(Label color: blue)" buttons "Thanks"
end if
if theTimeListNoCheck is not equal to "" then
display dialog "..." & theNotaNumberCounter & " file(s) were not checked for duration...
" & theTimeListNoCheck & "
(Label color: gray)" buttons "Thanks"
end if
if counter is not equal to 0 then
display dialog "..." & counter & " file(s) were not the proper format...
" & theList & "
(Label color: red)" buttons "Thanks"
end if
if counterTime is not equal to 0 then
display dialog "..." & counterTime & " file(s) were not the proper time...
" & theTimeList & "
(Label color: yellow)" buttons "Thanks"
end if
if theErrorFiles is not equal to 0 then
display dialog "..." & theErrorFiles & " file(s) were not correctly processed...
" & theErrorList & "
(Label color: purple)" buttons "Thanks"
end if
if counter is equal to 0 and theErrorFiles is equal to 0 and counterTime is equal to 0 and theTimeListNoCheck is equal to "" and theNoS4MList is equal to "" then
display dialog "All OK!" & "
(Label color: green)" buttons "Thanks"
end if
end tell
end open
--THIS HANDLER READS THE XML DATA:
on trackInfo given type:type as text, id:|id| as text : -1, key:key as text
local key, |id|, type
global tracks
tell application "System Events"
if |id| = -1 then
set {trackdata} to the |tracks| where ¬
value of the XML attribute "type" = |type|
else
set {trackdata} to the |tracks| where ¬
value of the XML attribute "type" = |type| and ¬
value of the XML attribute "streamid" = |id|
end if
tell (a reference to XML element named key in the trackdata) to ¬
if it exists then
return its value
else
return false
end if
end tell
end trackInfo
-- THIS HANDLER GENERATES THE XML
on mediainfoFile() -- Only call this once per script run
set theFile to quoted form of POSIX path of (thisFile)
set xmltext to do shell script "usr/local/bin/mediainfo " & ¬
theFile & " --Output=XML"
tell application "System Events"
set xml to make new XML data with properties {text:xmltext}
set mediaInfo to XML element "Mediainfo" of xml
set |file| to XML element "File" of mediaInfo
return {|tracks|:a reference to XML elements of |file|}
end tell
end mediainfoFile
-- THESE HANDLERS CONVERTS THE DURATION INFO TO NUMBERS
on returnNumbersInString(inputstring)
set s to quoted form of inputstring
do shell script "sed s/[a-zA-Z\\']//g <<< " & s
set dx to the result
set numlist to {}
repeat with i from 1 to count of words in dx
set this_item to word i of dx
try
set this_item to this_item as number
set the end of numlist to this_item
end try
end repeat
return numlist
end returnNumbersInString
on SAR(main_text, search_text, replace_text)
set old_delims to AppleScript's text item delimiters
try
set AppleScript's text item delimiters to search_text
considering case
set parts to every text item of main_text
end considering
set AppleScript's text item delimiters to replace_text
set newText to (parts as string)
end try
set AppleScript's text item delimiters to old_delims
return newText
end SAR
That’s a big script to try and go through, and one I have no means of really testing myself. But I do have two observations having had a skim through it:
① The variable thisFile is not defined in the scope of this handler. It’s a variable that gets instantiated for the repeat loop in your (implicit) run handler that loops through the batch of files; however, handlers (and other objects that contain code, such as script objects) have their own domain for storing and referencing variables that are fenced off from one another. So the variables in the main part of the script are only accessible from the main part of the script; and variables in the mediainfoFile() handler are only accessible from within the mediainfoFile() handler; likewise for the returnNumbersInString handler; and so on…
You may notice, however, that one of the handlers does access a variable outside of its domain:
--THIS HANDLER READS THE XML DATA:
on trackInfo given type:type as text, id:|id| as text : -1, key:key as text
local key, |id|, type
global tracks
.
.
.
It does this by declaring the variable tracks with global scope, meaning that the trackInfo handler gains access to that variable, even though it was originally declared outside the handler, in the main part of the script. I won’t go into this in much more detail other than to make you aware of this. Generally speaking, I wouldn’t suggest declaring variables as global all willy-nilly, because it makes it very difficult to keep track of them in a script, and very easy to alter their values in another part of the script without meaning to (the reason it suffices to do this for tracks is because I specifically created that variable to have one, and only one purpose, and a singular instance of being seen in the main part of the script—the moment it is first declared. It couldn’t be declared inside the trackinfo handler otherwise mediainfoFile() would be called every time you go to retrieve a piece of information from the same file).
What I recommend doing is declaring a parameter (or argument) in the mediainfoFile() handler, which will allow you to pass the value of the variable thisFile from the main part of the script to the handler at the point where you call it. So this line:
on mediainfoFile()
will become:
on mediainfoFile(thisFile)
Note: Don’t be confused by the fact I’ve used the same name for the argument as the variable in your repeat loop; you can give this argument any name you wish, as long as make sure to change it in the line that comes immediately after, which currently uses thisFile. Although the variable in the repeat loop and the argument plus variable used in the handler have the same name, they are distinct entities, and have no relation to one another.
Then call the handler passing thisFile as an argument like so:
set tracks to the mediainfoFile(theFile)'s tracks
② The second observation I’m going to make is a tendency I see a lot in novice scripters, which is to use error-catching try blocks a) unnecessarily; b) in large quantities; and c) that encompass many, many lines of code within a single block. The consequence of all of this is that you have no idea where your script is encountering problems; what the nature of those problems are; what is causing those problems; and, therefore, have no means to know how to debug/fix the script.
Don’t get me wrong: effective, thoughtful error-catching is a good, sensible facet of scripting and programming, but it should always have a specific purpose, and be used to catch an error that you can predict will happen, under a specific set of circumstances you know will arise in your script, and that you know and understand why the error gets thrown. If the try block is there purely to ensure execution of the script isn’t interrupted by some inconvenient error that comes out of nowhere when the try block is removed, then all it is doing is masking a sick, broken script. Chances are, after one line throws an error that gets caught and swept under the rug, the lines of code that follow are at risk of doing the same.
I would suggest deleting every single try block in your script; running it; and painstakingly going through each error that arises, finding out the nature of the error in Script Debugger’s event log, or at the time of alert, and fixing them one by one. If you can’t fix them, the script is damaged and unreliable; if you can fix them, you won’t need the try block. If there are instances where you do need to use one for a purpose, try and have it cover only one or two lines of code, specifically targetting where the error arises, and having an error handling block that does something useful to cater for the dysfunction so that it doesn’t have a ripple effect when the script exits the block.
Thank you for your help. I’m now able to get the variable to pass through in my script that only processes one at a time. For some reason in the larger script it still won’t define my PromoCodec variable. I think you are right about all the error checks in the original script. I understand why they are there (I was originally and end user of this script/application), but you are right in that it is making it impossible for me to debug.
What I’m going to try to do is rebuild the checks one piece at a time so that I can properly debug each step. I really appreciate your help.
Here is the working script that reads an individual file and returns it’s specs using MediaInfo CLI:
set TEST to (choose file)
set tracks to the mediainfoFile(TEST)'s tracks
set info to trackInfo given type:"Video", key:"Commercial_name"
if the info = false then error "Incorrect Codec"
set PromoCodec to (trackInfo given type:"Video", key:"Commercial_name")
set PromoBitRate to (trackInfo given type:"Video", key:"Bit_rate")
set PromoDuration to (trackInfo given type:"Audio", id:2, key:"Duration")
set PromoWidth to (trackInfo given type:"Video", key:"Width")
set PromoHeight to (trackInfo given type:"Video", key:"Height")
set PromoFrameRate to (trackInfo given type:"Video", key:"Frame_rate")
set FrameRate to returnNumbersInString(PromoFrameRate)
set Frames to SAR(FrameRate, ".", "")
set theDuration to returnNumbersInString(PromoDuration)
set Duration to SAR(theDuration, ", ", ".")
set PromoSpecs to {PromoCodec, PromoBitRate, FrameRate, Duration}
--HANDLERS:
on trackInfo given type:type as text, id:|id| as text : -1, key:key as text
local key, |id|, type
global tracks
tell application "System Events"
if |id| = -1 then
set {trackdata} to the |tracks| where ¬
value of the XML attribute "type" = |type|
else
set {trackdata} to the |tracks| where ¬
value of the XML attribute "type" = |type| and ¬
value of the XML attribute "streamid" = |id|
end if
tell (a reference to XML element named key in the trackdata) to ¬
if it exists then
return its value
else
return false
end if
end tell
end trackInfo
on mediainfoFile(inputstring) -- Only call this once per script run
set theFile to quoted form of POSIX path of (inputstring)
set xmltext to do shell script "usr/local/bin/mediainfo " & ¬
theFile & " --Output=XML"
tell application "System Events"
set xml to make new XML data with properties {text:xmltext}
set mediaInfo to XML element "Mediainfo" of xml
set |file| to XML element "File" of mediaInfo
return {|tracks|:a reference to XML elements of |file|}
end tell
end mediainfoFile
on returnNumbersInString(inputstring)
set s to quoted form of inputstring
do shell script "sed s/[a-zA-Z\\']//g <<< " & s
set dx to the result
set numlist to {}
repeat with i from 1 to count of words in dx
set this_item to word i of dx
try
set this_item to this_item as number
set the end of numlist to this_item
end try
end repeat
return numlist
end returnNumbersInString
on SAR(main_text, search_text, replace_text)
set old_delims to AppleScript's text item delimiters
try
set AppleScript's text item delimiters to search_text
considering case
set parts to every text item of main_text
end considering
set AppleScript's text item delimiters to replace_text
set newText to (parts as string)
end try
set AppleScript's text item delimiters to old_delims
return newText
end SAR
return PromoSpecs
I ran your script using the original XML data you provided, and it seemed to work okay.
The result returned was:
{"XDCAM HD422", "50.0 Mbps", {29.97}, "5.5"}
indicating the variable PromoCodec contained the value “XDCAM HD422”. Also, I note that variables trackInfo and PromoCodec both get assigned the same value as you make the same handler call twice (not a problem, just an observation I felt was odd).
So, currently, I cannot explain why your PromoCodec variable isn’t getting assigned a value. But you neglect to say what happens, i.e. does the script throw an error when it’s asked to set PromoCodec to …; and, if not, what result does the script return (specifically, what is the first item of PromoSpecs) ?
The MediaInfo/XML reader script I posted works to pull all the data properly.
It’s only when I try to shoehorn the working script into the larger application script with all the try blocks that I get the error. The error message is “The Variable PromoCodec is not defined”.
I’m now taking your suggestion and adding the checks and balances to the MediaInfo/XML reader script one at a time so that if/when I run into an error I can actually see where it is.
One question though. Once I have it working the way I want I’ll need to modify it so that I can process files as a batch.
Currently I choose the file to process like this:
set TEST to (choose file)
set tracks to the mediainfoFile(TEST)'s tracks
The mediainfoFile handler is set to:
on mediainfoFile(inputstring) -- Only call this once per script run
set theFile to quoted form of POSIX path of (inputstring)
set xmltext to do shell script "usr/local/bin/mediainfo " & ¬
theFile & " --Output=XML"
tell application "System Events"
set xml to make new XML data with properties {text:xmltext}
set mediaInfo to XML element "Mediainfo" of xml
set |file| to XML element "File" of mediaInfo
return {|tracks|:a reference to XML elements of |file|}
end tell
end mediainfoFile
This works perfectly with one file. If I wanted to process a batch of files, would this be the way to do it?:
on open theseFiles
repeat with thisFile in theseFiles
set TEST to thisFile
set tracks to the mediainfoFile(TEST)'s tracks
--mediainfoFile handler/subroutine
end repeat
on open theseFiles
repeat with thisFile in theseFiles
set TEST to thisFile
set tracks to the mediainfoFile(TEST)'s tracks
.
.
.
end repeat
end open
on mediainfoFile(inputstring)
.
.
.
end mediainfoFile
(* Other handlers *)
The mediainfoFilehandler (and all the other handlers in your script) belong outside the repeat loop, and outside the on open handler. But, otherwise, yes, the principle is correct.
I’ve re-written the entire script step by step. But I seem to still have an issue with this line:
repeat with thisFile in theseFiles -- STARTS CHECKING FILES IN SEQUENCE
set thePromo to thisFile
set tracks to the mediainfoFile(thePromo)'s tracks -- THIS LINE
These are the handlers I’m using that you gave me:
--THIS HANDLER READS THE XML DATA:
on trackInfo given type:type as text, id:|id| as text : -1, key:key as text
local key, |id|, type
global tracks
tell application "System Events"
if |id| = -1 then
set {trackdata} to the |tracks| where ¬
value of the XML attribute "type" = |type|
else
set {trackdata} to the |tracks| where ¬
value of the XML attribute "type" = |type| and ¬
value of the XML attribute "streamid" = |id|
end if
tell (a reference to XML element named key in the trackdata) to ¬
if it exists then
return its value
else
return false
end if
end tell
end trackInfo
--THIS HANDLER GENERATES THE XML DOCUMENT
on mediainfoFile(inputstring) -- Only call this once per script run
set theFile to quoted form of POSIX path of (inputstring)
set xmltext to do shell script "usr/local/bin/mediainfo " & ¬
theFile & " --Output=XML"
tell application "System Events"
set xml to make new XML data with properties {text:xmltext}
set mediaInfo to XML element "Mediainfo" of xml
set |file| to XML element "File" of mediaInfo
return {|tracks|:a reference to XML elements of |file|}
end tell
end mediainfoFile
For some reason when I try to use it as an application to check a batch of files, it doesn’t seem to activate the mediainfoFile handler and generate the XML. It works perfectly fine in my script that only does one file at a time. I thought I was following your instructions from a few posts ago properly, but I’m not sure what part I’m missing.
I really appreciate all your help, and I’m very close to finishing this and wouldn’t be this far without you.
I need more information to go on. Forget trying to get it to work as an application for now; focus on trying to get it to work as a script that contains a repeat loop. What goes wrong ? Where does it go wrong ? What’s the error message ? What is happening that shouldn’t happen ? What isn’t happening that should ?
Population one open handler with multiply selection:
set theseFiles to (choose file of type {"public.movie"} with multiple selections allowed)
on open theseFiles
repeat with thisFile in theseFiles
set TEST to thisFile
set tracks to the mediainfoFile(TEST)'s tracks
--mediainfoFile handler/subroutine
end repeat
This is a stripped down version of the script that I’m working with to try and figure out the problem:
set theseFiles to (choose file with multiple selections allowed)
repeat with thisFile in theseFiles
set tracks to the mediainfoFile(thisFile)'s tracks
set PromoCodec to (trackInfo given type:"Video", key:"Commercial_name")
set thename to name of (info for the thisFile) & " " & PromoCodec & " "
return thename
end repeat
--THIS HANDLER READS THE XML DATA:
on trackInfo given type:type as text, id:|id| as text : -1, key:key as text
local key, |id|, type
global tracks
tell application "System Events"
if |id| = -1 then
set {trackdata} to the |tracks| where ¬
value of the XML attribute "type" = |type|
else
set {trackdata} to the |tracks| where ¬
value of the XML attribute "type" = |type| and ¬
value of the XML attribute "streamid" = |id|
end if
tell (a reference to XML element named key in the trackdata) to ¬
if it exists then
return its value
else
return false
end if
end tell
end trackInfo
--THIS HANDLER GENERATES THE XML DOCUMENT
on mediainfoFile(inputstring) -- Only call this once per script run
set theFile to quoted form of POSIX path of (inputstring)
set xmltext to do shell script "usr/local/bin/mediainfo " & ¬
theFile & " --Output=XML"
tell application "System Events"
set xml to make new XML data with properties {text:xmltext}
set mediaInfo to XML element "Mediainfo" of xml
set |file| to XML element "File" of mediaInfo
return {|tracks|:a reference to XML elements of |file|}
end tell
end mediainfoFile
on returnNumbersInString(inputstring)
set s to quoted form of inputstring
do shell script "sed s/[a-zA-Z\\']//g <<< " & s
set dx to the result
set numlist to {}
repeat with i from 1 to count of words in dx
set this_item to word i of dx
try
set this_item to this_item as number
set the end of numlist to this_item
end try
end repeat
return numlist
end returnNumbersInString
So far this only seems to process the first file in the list, no matter how many I choose. I don’t think I’m using the repeat block properly.
Ok, I’ve got the repeat step working with this script:
set theseFiles to (choose file with multiple selections allowed)
set thelist to ""
repeat with x from 1 to count of theseFiles
set thisFile to item x of theseFiles
set tracks to the mediainfoFile(thisFile)'s tracks
set PromoCodec to (trackInfo given type:"Video", key:"Commercial_name")
set thename to name of (info for the thisFile) & " " & PromoCodec & " "
set thelist to thelist & " " & thename & " "
end repeat
return thelist
--THIS HANDLER READS THE XML DATA:
on trackInfo given type:type as text, id:|id| as text : -1, key:key as text
local key, |id|, type
global tracks
tell application "System Events"
if |id| = -1 then
set {trackdata} to the |tracks| where ¬
value of the XML attribute "type" = |type|
else
set {trackdata} to the |tracks| where ¬
value of the XML attribute "type" = |type| and ¬
value of the XML attribute "streamid" = |id|
end if
tell (a reference to XML element named key in the trackdata) to ¬
if it exists then
return its value
else
return false
end if
end tell
end trackInfo
--THIS HANDLER GENERATES THE XML DOCUMENT
on mediainfoFile(inputstring) -- Only call this once per script run
set theFile to quoted form of POSIX path of (inputstring)
set xmltext to do shell script "usr/local/bin/mediainfo " & ¬
theFile & " --Output=XML"
tell application "System Events"
set xml to make new XML data with properties {text:xmltext}
set mediaInfo to XML element "Mediainfo" of xml
set |file| to XML element "File" of mediaInfo
return {|tracks|:a reference to XML elements of |file|}
end tell
end mediainfoFile
But for some reason once I add the “on open” command to try and make it into an application that’s where my variables stop populating properly. Here’s the same script as above, but with the added “on open” step to try and make it an application:
set theseFiles to (choose file with multiple selections allowed)
set thelist to ""
on open theseFiles
repeat with x from 1 to count of theseFiles
set thisFile to item x of theseFiles
set tracks to the mediainfoFile(thisFile)'s tracks
set PromoCodec to (trackInfo given type:"Video", key:"Commercial_name")
set thename to name of (info for the thisFile) & " " & PromoCodec & " "
set thelist to thelist & " " & thename & " "
end repeat
end open
display dialog "..." & thelist
--THIS HANDLER READS THE XML DATA:
on trackInfo given type:type as text, id:|id| as text : -1, key:key as text
local key, |id|, type
global tracks
tell application "System Events"
if |id| = -1 then
set {trackdata} to the |tracks| where ¬
value of the XML attribute "type" = |type|
else
set {trackdata} to the |tracks| where ¬
value of the XML attribute "type" = |type| and ¬
value of the XML attribute "streamid" = |id|
end if
tell (a reference to XML element named key in the trackdata) to ¬
if it exists then
return its value
else
return false
end if
end tell
end trackInfo
--THIS HANDLER GENERATES THE XML DOCUMENT
on mediainfoFile(inputstring) -- Only call this once per script run
set theFile to quoted form of POSIX path of (inputstring)
set xmltext to do shell script "usr/local/bin/mediainfo " & ¬
theFile & " --Output=XML"
tell application "System Events"
set xml to make new XML data with properties {text:xmltext}
set mediaInfo to XML element "Mediainfo" of xml
set |file| to XML element "File" of mediaInfo
return {|tracks|:a reference to XML elements of |file|}
end tell
end mediainfoFile
Is the code different when trying to make an application? I’m obviously missing something.
What you get is logical.
When your script execute return theName it exits the loop.
Try with:
set theseFiles to (choose file with multiple selections allowed)
set theNames to {}
repeat with thisFile in theseFiles
set tracks to the mediainfoFile(thisFile)'s tracks
set PromoCodec to (trackInfo given type:"Video", key:"Commercial_name")
set thename to name of (info for the thisFile) & " " & PromoCodec & " "
set end of theNames to thename
end repeat
return theNames
# Here are the handlers
Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 13 mai 2019 17:41:53
Here is the almost finished script. It works exactly how I need it to. It can read multiple files and the error checks I’ve built in process everything correctly. The only step left is to figure out how to make it work as a droplet/application. Thank you all for your help getting me this far.
set theseFiles to (choose file with multiple selections allowed)
-- THESE ARE THE ERROR VARIABLES
set CodecError to 0 --Codec Errors will highlight the file GRAY
set DurationError to 0 --Duration Errors will highlight the file YELLOW
set InvalidDuration to 0
set InvalidS4MNumber to 0
set TotalCodecErrors to 0 -- COUNTS CODEC ERRORS
set TotalDurationErrors to 0 -- COUNTS PROMO DURATION ERRORS
set TotalS4MNumberErrors to 0 -- COUNTS BAD S4M NUMBERS IN FILENAMES
set TotalFileNameErrors to 0 -- COUNTS DURATION ERRORS OF TOP OF FILENAMES
set TotalPromoError to 0 -- RESULTS IN "FILE NOT PROPERLY PROCESSED"
set FilesWithCodecErrors to "" -- CREATES A LIST OF THE PROMOS THAT HAVE CODEC ERRORS
set FilesWithDurationErrors to "" -- CREATES A LIST OF THE PROMOS THAT HAVE DURATION ERRORS
set FilesWithFileNameErrors to "" -- CREATES A LIST OF THE PROMOS THAT HAVE FILENAME ERRORS
set FilesWithS4MNumberErrors to "" -- CREATES A LIST OF THE PROMOS THAT HAVE S4M NUMBER ERRORS
set FilesNotProcessed to "" -- CREATES A LIST OF PROMOS THAT WERE NOT PROCESSED
set theRightFormats to {"XDCAM HD422", "50.0 Mbps", "29.970 fps"} as list
repeat with x from 1 to count of theseFiles -- STARTS CHECKING FILES IN SEQUENCE
set thePromo to item x of theseFiles
set tracks to the mediainfoFile(thePromo)'s tracks
--WHEN MEDIAINFO READS AN MXF WITH XDCAM HD422 AS THE CODEC THE FIELD IS LABELLED
--"Commercial_Name". THIS TRY BLOCK CHECKS THE CODEC FIELD OF THE XML
--TO MAKE SURE THE PROPER CODEC FIELD IS PRESENT
try
set PromoCodec to (trackInfo given type:"Video", key:"Commercial_name")
if the PromoCodec = false then
set CodecError to 1
end if
--IF THE PROPER CODEC IS PRESENT THIS IS WHERE THE VARIABLES ARE POPULATED BY THE MEDIAINFOFILE HANDLER
if CodecError is equal to 0 then
set PromoBitRate to (trackInfo given type:"Video", key:"Bit_rate")
set PromoDuration to (trackInfo given type:"Audio", id:2, key:"Duration")
set PromoFrameRate to (trackInfo given type:"Video", key:"Frame_rate")
set theDuration to returnNumbersInString(PromoDuration)
set Duration to SAR(theDuration, ", ", ".")
set FileName to do shell script "basename " & quoted form of POSIX path of thePromo
set S4MDuration to the first word of FileName
end if
end try
--THIS SECTION IS WHERE THE TRUNCATED FILE NAME IS CREATED
try
tell application "Finder"
set extension hidden of thePromo to false
end tell
set theName to name of (info for thePromo)
set thePath to the POSIX path of thePromo
set n to the number of characters in theName
set n to (n - 11)
set JustS4MNumber to (characters n through end of theName) as string
set theNewPath to quoted form of ("/Users/jquimby/Desktop/Proxies/" & JustS4MNumber)
--THE ABOVE LINE IS WHERE THE FILE WILL BE POSTED IF IT PASSES ALL TECH CHECKS
set theOldpath to quoted form of thePath
set PromoError to 0
on error
set PromoError to 1
end try
--THIS IS THE BEGINNING OF THE TECH CHECKS
if PromoError is equal to 0 and CodecError is equal to 0 then
set theS4MNumber to the word -2 of theName --word -2 removes the file extension
set S4MNumberCheck to the first word of JustS4MNumber
--THIS SECTION CHECKS THAT THE FIRST WORD OF THE FILE NAME IS ACTUALLY A VALID NUMBER
try
set DurationCheck to (S4MDuration + 2)
on error
set InvalidDuration to 1
end try
--THIS SECTION ENSURES THE PROMO DURATION IS WITHIN A 2 FRAME RANGE OF THE TARGET DURATION
if InvalidDuration is equal to 0 then
set FramesHeavy to (S4MDuration + 0.66)
set FramesLight to (S4MDuration - 0.66)
if Duration is greater than FramesLight and Duration is less than FramesHeavy then
set DurationError to 0
else
set DurationError to 1
end if
end if
--THIS SECTION CHECKS THAT THE S4M NUMBER IS 8 DIGITS AND CONTAINS ONLY NUMBERS
if the number of characters of theS4MNumber is equal to 8 then
try
set theS4MCalc to (theS4MNumber + 2)
on error
set InvalidS4MNumber to 1
end try
else
set InvalidS4MNumber to 1
end if
if the number of characters of S4MNumberCheck is not equal to 8 then
set InvalidS4MNumber to 1
end if
--THIS SECTION CHECKS TO ENSURE THE CODEC AND BIT RATE ARE CORRECT
if PromoCodec is not in theRightFormats and PromoBitRate is not in theRightFormats then
set CodecError to 1
else
set CodecError to 0
end if
end if
--THIS SECTION CHECKS ALL THE ERROR COUNTERS, LABELS THE FILES IN FINDER, CREATES THE ERROR LIST, AND IF THE FILE PASSES ALL CHECKS IT WILL PASTE THE FILE INTO THE DESIRED DESTINATION
if PromoError is equal to 0 then
tell application "Finder"
if CodecError is not equal to 0 then -- CODEC ERROR CHECK
set label index of thePromo to 2
set TotalCodecErrors to TotalCodecErrors + 1
set FilesWithCodecErrors to FilesWithCodecErrors & theName & "" & " • codec is " & PromoCodec & "
"
set CodecError to 0
else
if InvalidS4MNumber is not equal to 0 then -- BAD S4M NUMBER FORMAT IN FILENAME
set label index of thePromo to 4
set FilesWithS4MNumberErrors to FilesWithS4MNumberErrors & theName & "
"
set TotalS4MNumberErrors to TotalS4MNumberErrors + 1
set InvalidS4MNumber to 0
else
if DurationError is not equal to 0 then -- PROMO IS NOT WITHIN THE 2 FRAME BUFFER FOR DURATION
set label index of thePromo to 7
set TotalDurationErrors to TotalDurationErrors + 1
set FilesWithDurationErrors to FilesWithDurationErrors & theName & "
"
set DurationError to 0
else
if InvalidDuration is not equal to 0 then -- BAD DURATION FORMAT IN FILENAME
set label index of thePromo to 3
set TotalFileNameErrors to TotalFileNameErrors + 1
set FilesWithFileNameErrors to FilesWithFileNameErrors & theName & "
" & " • length is " & (round (Duration / 30)) & " seconds
"
set InvalidDuration to 0
else
set label index of thePromo to 6
do shell script "cp " & theOldpath & " " & theNewPath --THIS IS THE STEP THAT ACTUALLY COPIES THE FILE TO THE NEW LOCATION
end if
end if
end if
end if
end tell
set PromoError to 0
end if
if PromoError is not equal to 0 then
tell application "Finder"
set label index of thePromo to 5
set TotalPromoError to TotalPromoError + 1
set FilesNotProcessed to FilesNotProcessed & (name of (info for thePromo)) & "
"
end tell
end if
end repeat -- END OF INDIVIDUAL FILE CHECK REPEAT LOOP
--THIS IS WHERE THE BATCH ERROR MESSAGES BEGIN
tell application "Finder"
if FilesWithCodecErrors is not equal to "" then
display dialog "..." & TotalCodecErrors & " file(s) are not the proper codec...
" & FilesWithCodecErrors & "
(Label colour: red)" buttons "Thanks"
end if
if FilesWithS4MNumberErrors is not equal to "" then
display dialog "..." & TotalS4MNumberErrors & " file(s) do not have a proper S4M number...
" & FilesWithS4MNumberErrors & "(Label colour: blue)" buttons "Thanks"
end if
if FilesWithDurationErrors is not equal to "" then
display dialog "..." & TotalDurationErrors & " file(s) were not the proper duration...
" & FilesWithDurationErrors & "(Label colour: yellow)" buttons "Thanks"
end if
if FilesWithFileNameErrors is not equal to "" then
display dialog "..." & TotalFileNameErrors & " file(s) had incorrect durations in their file name(s).
" & FilesWithFileNameErrors & "(Label colour: purple)" buttons "Thanks"
end if
if FilesNotProcessed is not equal to "" then
display dialog "..." & TotalPromoError & " file(s) were not properly processed.
" & FilesNotProcessed & "(Label colour: orange)" buttons "Thanks"
end if
if FilesWithCodecErrors is equal to "" and FilesWithS4MNumberErrors is equal to "" and FilesWithDurationErrors is equal to "" and FilesWithFileNameErrors is equal to "" and FilesNotProcessed is equal to "" then
display dialog "All OK!" & theName & "
(Label colour: green)" buttons "Thanks"
end if
end tell
--THIS HANDLER READS THE XML DATA:
on trackInfo given type:type as text, id:|id| as text : -1, key:key as text
local key, |id|, type
global tracks
tell application "System Events"
if |id| = -1 then
set {trackdata} to the |tracks| where ¬
value of the XML attribute "type" = |type|
else
set {trackdata} to the |tracks| where ¬
value of the XML attribute "type" = |type| and ¬
value of the XML attribute "streamid" = |id|
end if
tell (a reference to XML element named key in the trackdata) to ¬
if it exists then
return its value
else
return false
end if
end tell
end trackInfo
--THIS HANDLER GENERATES THE XML DATA
on mediainfoFile(inputstring) -- Only call this once per script run
set theFile to quoted form of POSIX path of (inputstring)
set xmltext to do shell script "usr/local/bin/mediainfo " & ¬
theFile & " --Output=XML"
tell application "System Events"
set xml to make new XML data with properties {text:xmltext}
set mediaInfo to XML element "Mediainfo" of xml
set |file| to XML element "File" of mediaInfo
return {|tracks|:a reference to XML elements of |file|}
end tell
end mediainfoFile
--THIS HANDLER EXTRACTS NUMBERS FROM A STRING
on returnNumbersInString(inputstring)
set s to quoted form of inputstring
do shell script "sed s/[a-zA-Z\\']//g <<< " & s
set dx to the result
set numlist to {}
repeat with i from 1 to count of words in dx
set this_item to word i of dx
try
set this_item to this_item as number
set the end of numlist to this_item
end try
end repeat
return numlist
end returnNumbersInString
--THIS HANDLER SEARCHES A STRING FOR SPECIFIED CHARACTERS AND REPLACES THEM WITH THE DESIGNATED CHARACTERS
on SAR(main_text, search_text, replace_text)
set old_delims to AppleScript's text item delimiters
try
set AppleScript's text item delimiters to search_text
considering case
set parts to every text item of main_text
end considering
set AppleScript's text item delimiters to replace_text
set newText to (parts as string)
end try
set AppleScript's text item delimiters to old_delims
return newText
end SAR
I thought it would be as simple as changing:
set theseFiles to (choose file with multiple selections allowed)
to:
on open theseFiles
then adding an “End Open” after I end the repeat. But I start getting the “PromoCodec” not defined error again. Is there something I’m missing in modifying the script so it can work as a droplet?
Save the code below as an application.
It will behave as a droplet if you dop selected files on its icon.
on run
set theseFiles to (choose file with multiple selections allowed)
common(theseFiles)
end run
on open sel
common(sel)
# CAUTION. Given a feature of the open handler, the list of files may be trated as two separate lists according to the quarantine status of the dropped files.
end open
on common(theseFiles)
# Here most of your code
# Fake code
set thePaths to my recolle(theseFiles, linefeed)
display dialog thePaths
end common
# Now your existing handlers
#===== Just to show the structure at work
on recolle(l, d)
local oTIDs, t
set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, d}
set t to l as text
set AppleScript's text item delimiters to oTIDs
return t
end recolle
#=====
Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 13 mai 2019 19:53:54
set theseFiles to (choose file with multiple selections allowed)
set thelist to {}
repeat with i from 1 to count of theseFiles
set thisFile to item i of theseFiles
set tracks to the mediainfoFile(thisFile)'s tracks
set PromoCodec to (trackInfo given type:"Video", key:"Commercial_name")
set thename to name of (info for the thisFile) & space & PromoCodec
set the end of thelist to thename
end repeat
return thelist
This returns an AppleScript list that looks like this: (I used the same infofile twice)[format]{“mediainfo.xml XDCAM HD422”, “mediainfo.xml XDCAM HD422”}[/format]If you want it as a text-based list, say, one item per line, then:
set the text item delimiters to linefeed
return thelist as text
which produces this:[format]“mediainfo.xml XDCAM HD422
mediainfo.xml XDCAM HD422”[/format]
It feels a bit awkward having to resort to using a counter variable to iterate through the list. The reason you felt you had to do that is because of the way the two different types of loop reference the items in the list: using a counter variable as above, one retrieves the list item directly:
set thisFile to item i of theseFiles
Using an iterator, the items in the list are retrieved as references, and need to be dereferenced (or evaluated) first:
repeat with thisFile in theseFiles
thisFile as alias
--OR:
thisFile's contents
.
.
.
end repeat
Therefore, you may find the problem that was solved by using the counter variable also gets solved like this:
set theseFiles to (choose file with multiple selections allowed)
set thelist to {}
repeat with thisFile in theseFiles
set tracks to the mediainfoFile(thisFile as alias)'s tracks
set PromoCodec to (trackInfo given type:"Video", key:"Commercial_name")
set thename to name of (info for the thisFile) & space & PromoCodec
set the end of thelist to thename
end repeat
return thelist
I’m still not a fan of the way you blanket lines of code within try blocks that don’t need it. Here’s an example:
try
tell application "Finder"
set extension hidden of thePromo to false
end tell
set theName to name of (info for thePromo)
set thePath to the POSIX path of thePromo
set n to the number of characters in theName
set n to (n - 11)
set JustS4MNumber to (characters n through end of theName) as string
set theNewPath to quoted form of ("/Users/jquimby/Desktop/Proxies/" & JustS4MNumber)
--THE ABOVE LINE IS WHERE THE FILE WILL BE POSTED IF IT PASSES ALL TECH CHECKS
set theOldpath to quoted form of thePath
set PromoError to 0
on error
set PromoError to 1
end try
This was chosen at random, actually, but having gone through each line individually, I can’t discern how any of them would ever throw an error; and, if there were a risk, how there wouldn’t be a very simple way to prevent it, or rectify it upon catching.
One line that is very potentially capable of throwing an error (and it would only be through bad luck and not poor coding), is set extension hidden of thePromo to false. Assuming thePromo is a well-formed file reference that Finder can understand, then the only risk is from Finder being temperamental: so, if it were busy, or it just decided not to let you access the property, it might error. Simply waiting until Finder is not blocked and then re-running the script solves that problem.
However, consider whether you need to set this property at all… It looks like you might be doing it to ensure theName is assigned the full filename including extension, however this property is not affected by the visibility of the extension in Finder (only the displayed name accounts for this). If you do need to set the property, then you may as well move the declaration of theName into the Finder block, and use Finder to access the name (rather than the deprecated info for). It doesn’t make a huge difference, given that info for has been deprecated for about ten years and still works perfectly reliably and actually much faster than Finder. I’ll let you decide.
The only other line capable of throwing an error is: set JustS4MNumber to (characters n through end of theName) as string. Firstly, I’m going to suggest you re-write this line to:
set JustS4MNumber to text n thru -1 of theName
The reason for this is that retrieving characters produces a list, which you then have to coerce back to text, and that is subject to text item delimiters, which you haven’t set. Instead, obtain a text range gives you the substring as one chunk of text, so there’s no need to coerce and no risk of getting back a string you didn’t expect. Now, the only way this can throw an error is if n is 0, i.e. if theName happened to be 11 characters long.
In summation for this particular try block, I would suggest it is completely unnecessary, and you simply need to ensure the thePromo is a valid file reference; and n isn’t 0 (or, rather, the name of your files are at least 12 characters long).
I see Yvan has given some guidance about moving the script to an application.
on open theseFiles
return choose from list paragraphs of (theseFiles as text)
which will allow you to examine exactly what list of files is getting passed through to the on open handler. Given what Yvan noted about the possibility of split lists of files if some are quarantined, resulting in theseFiles being a list of lists, you might see if it makes a difference were you to flatten the list and guarantee it is one-dimensional. Here’s a simple flatten() handler:
to flatten(L)
local L
if L = {} then return {}
if L's class ≠ list then return {L}
flatten(L's first item) & flatten(rest of L)
end flatten
To illustrate its function:
set L to {{1, 3, 4, {4, 5, {4, {{5}}, 5}, 5, {4, 3, 4}}, 4, 2, 3, 3}, {2, 3, {4, 5}}, 4}
flatten(L)