Convert “localhost” to your Mac’s current IP address

When developing web pages, I use MAMP.app or my Mac’s built-in Apache. Viewing the page means using an address such as http://localhost/mypage.html. If you use custom host names (especially easy with the excellent VirtualHostX.app), you may wind up with a localhost address such as http://projectname/mypage.html.

This works great when you’re just testing the pages on your Mac’s browsers. However, once you cross boundaries into Windows testing (via VMs or separate laptops), localhost will no longer resolve. Why? Because localhost is local to your machine.

If you want to view the page in a VM or on another machine, just swap the domain name with your machine’s IP address. For example http://localhost/mypage.html becomes http://10.0.1.14/mypage.html. (Note: you must be on the same network or have a public IP address.)

This works very well, but it’s tiresome to manually grab the IP address anytime you want to use a VM or share the page with coworkers, especially if you’re on DHCP and don’t have a static IP address.

I decided to make my life a little easier by writing an AppleScript that looks at the open tabs in Chrome and Safari then replaces “localhost” (or custom domain) with my current IP address. Saving this as a service enables me to go to Chrome > Services to run the script.

Chrome > Services

If you’d like to give it a try, the AppleScript is available as a Gist on GitHub.

Advertisements

AppleScript for generating SCORM manifest nodes

SCORM requires all of the course assets to be listed as a <file> item in the <resource> node. This is not evenly enforced — some LMSs don’t care of you do it or not — but is still a good practice.

If you’re anything like me, you find it to be a major pain and try to avoid it.

Today I decided to whip up an AppleScript that automates the generation of the <file> nodes to make my life a little easier. If you’re on a Mac, you may find it useful, too. I’ve posted it on GitHub as gist:

https://gist.github.com/pipwerks/9179518

Note that it doesn’t include the name of the root folder. Let’s say you have a root folder named content. If needed, you can simply specify the root using the “xml:base” attribute of the resource node, like so:


<resource identifier="reosurceID" adlcp:scormType="sco" href="index.html" type="webcontent" xml:base="content/">
   <file href="index.html" />
   <file href="Lesson01/index.html" />
</resource>

Clean out the root of your SCORM 2004 package

Anyone who works with SCORM 2004 has seen something like this:

Image of file directory with all schema files at root of directory

With just a little effort, you can make it look like this, and still be perfectly valid:

Image of file directory with all schema files placed in subfolder

SCORM manifests are required to specify a slew of schema files via the schemaLocation attribute. Here’s what you’d typically see:


<manifest identifier="pipwerks-schema-example" version="1.0"
          xmlns="http://www.imsglobal.org/xsd/imscp_v1p1" 
          xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_v1p3" 
          xmlns:adlseq="http://www.adlnet.org/xsd/adlseq_v1p3" 
          xmlns:adlnav="http://www.adlnet.org/xsd/adlnav_v1p3" 
          xmlns:imsss="http://www.imsglobal.org/xsd/imsss" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
          xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd 
                              http://www.adlnet.org/xsd/adlcp_v1p3 adlcp_v1p3.xsd 
                              http://www.adlnet.org/xsd/adlseq_v1p3 adlseq_v1p3.xsd 
                              http://www.adlnet.org/xsd/adlnav_v1p3 adlnav_v1p3.xsd 
                              http://www.imsglobal.org/xsd/imsss imsss_v1p0.xsd">

Notice the structure of the data in the schemaLocation attribute: external URL followed by a space then the local (relative) URL. For example:


http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd

In this example, imscp_v1p1.xsd is at the root of the package, in the same folder as the imsmanifext.xml file. The trick is to create a subfolder in the root of the package, then update schemaLocation to point to the subfolder. I created a subfolder named SCORM-schemas, which you can see in the following code exerpt:


<manifest identifier="pipwerks-schema-example" version="1.0"
          xmlns="http://www.imsglobal.org/xsd/imscp_v1p1" 
          xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_v1p3" 
          xmlns:adlseq="http://www.adlnet.org/xsd/adlseq_v1p3" 
          xmlns:adlnav="http://www.adlnet.org/xsd/adlnav_v1p3" 
          xmlns:imsss="http://www.imsglobal.org/xsd/imsss" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
          xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 SCORM-schemas/imscp_v1p1.xsd 
                              http://www.adlnet.org/xsd/adlcp_v1p3 SCORM-schemas/adlcp_v1p3.xsd 
                              http://www.adlnet.org/xsd/adlseq_v1p3 SCORM-schemas/adlseq_v1p3.xsd 
                              http://www.adlnet.org/xsd/adlnav_v1p3 SCORM-schemas/adlnav_v1p3.xsd 
                              http://www.imsglobal.org/xsd/imsss SCORM-schemas/imsss_v1p0.xsd">

Test, test, test! I’ve tested this in SCORM Cloud as well as a couple of real-world LMSs and haven’t encountered any issues. Your mileage may vary depending on your LMS’s SCORM implementation, but this is perfectly valid XML and shouldn’t break in any LMSs — unless the LMS is poorly coded, but that’s a rarity, right? (LOL)

I guess there’s no such thing as a secure PDF

I was reading the SCORM 1.2 reference docs today. I wanted to copy a passage for my notes, but the PDF is password-protected and prevents anyone from copying text. (REALLY irritating, considering the ADL is a quasi-government organization and the docs should be open to all.)

What to do? Well, turns out there are at least two super easy ways to bypass the password protection: Upload it to Google Drive or import it to Evernote.

Google Drive

The Google Drive site includes a built-in PDF reader; when I opened the PDF in the web viewer, I was able to copy text freely. Better yet, I was able to save the PDF as an unlocked file by selecting “Print” then choosing “Save as PDF” in the print options.

Evernote

When dragging the file onto the Evernote app (Mac), the PDF shows up in a preview window. I was able to copy text freely. No need to save as a PDF since it’s already stored in Evernote!

Security, schmescurity

So I guess there’s no such thing as a secure PDF. I’m sure there are other services like to Google Drive and Evernote, and there are definitely other techniques for defeating protection, including screen captures, OCR, and the old fashioned approach of printing to paper then scanning the prints. If you truly need a document to be secure, don’t distribute it electronically.

iTunes, TV Shows and Apple TV

iTunes vexes me. For better or for worse, we’re an Apple household and own an Apple TV, so I’m kind of stuck with iTunes for managing my media files.

My wife and I have also purchased a significant amount of DVDs over the years, which I ripped to iTunes using the trusty old Handbrake (love you, Handbrake!). These DVDs include a lot of TV shows, such as Doctor Who and Magnum PI.

My workflow has always been: rip via Handbrake, then import into iTunes by dragging the m4v files onto the iTunes window. By default, the TV shows don’t have any metadata (no proper titles, descriptions, episode numbers, or artwork), and iTunes automatically files them under Movies. This means they’ll show up in Apple TV with no description, no preview picture (such as DVD box art), and no sequence information.

I recently heard someone mention iDentify, a Mac app that adds metadata to movie files. It’s not free, so I had reservations about buying it. However, $10 is a small price to pay for cleaning up such a big mess, especially if you’re a bit OCD like me. I decided to give it a try, and it works very well, especially for TV shows — if you manually specify each file’s season and episode number, iDentify will take care of the rest by performing lookups at thetvdb.com. Sweet.

iDentify took care of the metadata and artwork problem, but the files were still cluttering my Movies menu, making it very hard to navigate with a remote control. For example, Magnum PI went eight seasons and has over 150 episodes, so we’d have to navigate past 150 Magnum PI titles to get to any videos whose name began with N-Z. Very annoying.

For a long time my workaround was to create custom genres and shove the TV shows there, then stick to genres when navigating Apple TV. This always felt kludgy, and I wondered why I couldn’t just drag the TV show episodes onto the TV Shows section in iTunes. This weekend I decided to look into it, and stumbled onto a MacWorld article containing a solution so simple I had to do a double face-palm: change the Media Kind from Movie to TV Show.

iTunes file properties dialog, 'Options' tab

Once set, the video is automagically moved from the iTunes Media/Movies folder to the iTunes Media/TV Shows folder, and shows up in the TV Shows menu!

Be sure to input the show’s name in the Video section so the episodes will be properly grouped.

iTunes file properties dialog, 'Video' tab

The MacWorld article pointed out that this technique can be extended to group ANY videos. This piqued my interest — my wife and I own a lot of DVDs that contain high-quality special features, including the entire James Bond collection, Star Wars collection, and classic films like Lawrence of Arabia. As I mentioned, I’m partially OCD, so I’ve ripped quite a few of these special features. Until now, they’ve all cluttered up my Movies menu just like the TV shows did.

TV Show grouping to the rescue! By changing the videos’ Media Kind to TV Show, they get moved to the TV Show section and can then be grouped. For example, I grouped all of my James Bond special features under the heading “James Bond Featurettes”. Now when I navigate the TV Shows section of iTunes or Apple TV, I only see ONE listing for James Bond Featurettes and no longer need to sift through 100+ titles.

iTunes still leaves a lot to be desired, but I’m a happy camper now that my files are well-organized and have proper metadata.

Setting OS X Desktop Picture Based on Time of Day

I recently changed jobs (Hello, FireEye!) and was issued a new MacBook Air. I spend a lot of time looking at the screen and was getting bored with the supplied desktop pictures. I also start work very early most days (7am-ish), and thought it would be nice to have a desktop picture that matches the mellow-ness of such an early hour.

Of course, this leads to daydreaming — “scope creep” in professional parlance — and next thing you know, I started thinking “well, maybe I could also set it to show a nice evening-themed picture at night”. Then “maybe I can get it to change both screens” (I use a laptop with an external display).

I also liked the challenge of putting together a script as quickly as possible. (In my off-hours, of course!)

I downloaded some nice wallpaper images from National Geographic, then created six folders that correspond to the major periods of the day: morning (early and late), afternoon (early and late), and evening (early and late). I organized my National Geographic photos into those six folders, based on the mood each photo evokes. For example, this one is an early morning photo.

Then I rolled up my sleeves and got out the trusty old AppleScript Editor. The resulting AppleScript is posted on GitHub, if you’d like to take a gander.

The gist:

  • It selects a folder based on the time of day.
  • It randomly selects an image from within that folder and displays it as the desktop picture.
  • It supports more than one monitor, with an option to either display the same image on all monitors, or display different images on each monitor.

The resulting AppleScript must be run at a regularly scheduled interval. I’m currently using GeekTool to run the script every 15 minutes, but I might eventually switch to a crontab job for less overhead.

Regardless, I’m quite happy with the way it turned out, and have already started daydreaming about other things I can hack together with AppleScript.

Important Adobe Captivate SCORM template update

Over the last few weeks, I received a few reports that scores were not being saved in the LMS when using my template. Turned out to be a simple oversight on my part, which I have just fixed. Please download the latest version of scorm_support.js (v1.20120328) from GitHub.

Cause and effect

If you’re curious what happened, here’s a quick rundown:

When a SCORM course launches for the first time, the value of cmi.completion_status is "ab-initio". This means the course is a fresh launch with no prior completion attempts, and therefore no historical data in the LMS.

When Captivate launches, it requests a slew of information from the LMS via SCORM_API.GetValue. This includes the usual suspects, such as completion status, suspend data, location, score.raw, score.max, score.min, and score.scaled. However, if the course has never been launched before, suspend_data, location, and the score elements will all be empty (null). If the LMS follows the SCORM spec, it will throw the “element not initialized” error.

In my earlier work on the template, I decided to prevent these “element not initialized” errors by adding some logic to the template, preventing suspend_data, location, and the score elements from being checked when the course status is ab-initio. This was achieved via a regular expression:


if(entryStatus === "ab-initio" && /location|suspend_data|score/g.test(parameter)){
   //prevent action
}

Unfortunately, I overlooked one important detail: when the Captivate course loads, it queries the LMS to see which SCORM fields are supported. This is done by requesting the “._children” CMI elements. For example, cmi.score._children will return the string “scaled,min,max,raw” indicating that cmi.score.scaled, cmi.score.min, cmi.score.max, and cmi.score.raw are supported by the LMS.

See any problems yet?

My regular expression was too broad, and prevented cmi.score._children from being queried, making Captivate believe that cmi.score was not supported. Since Captivate thought cmi.score was not supported, it did the right thing and stopped sending cmi.score data to the LMS.

The solution was to make the regular expression a bit more explicit:


if(entryStatus === "ab-initio" && /location|suspend_data|score\.(raw|min|max|scaled)/g.test(parameter)){
   //prevent action
}

Instead of blocking any GetValue calls requesting “score” data when the course is ab-initio, we now only block GetValue calls that request specific CMI elements: score.raw, score.min, score.max, and score.scaled. Problem solved.

New SCORM 1.2 Template for Adobe Captivate

By popular demand, the SCORM 1.2 edition of my revised SCORM publishing templates for Adobe Captivate 5.x is now available on GitHub.

Instructions can be found here.

While testing the SCORM 1.2 revisions, I noticed Captivate sometimes sends invalid data to the LMS, specifically for cmi.interactions.n.correct_responses.n.pattern, cmi.interactions.n.student_response, and cmi.interactions.n.weighting. I may fix these errors in a future update, but they’re relatively harmless, so I’ll leave them be for now.

Further Tweaks to the Adobe Captivate SCORM Publishing Template

Now that my version of the Adobe Captivate publishing template for SCORM 2004 is on GitHub, it has become a living document, bound to get updates (major and minor) from time to time. For those of you unfamiliar with GitHub, it’s a nifty site for storing code; it provides issues list for tracking bugs, it enables people to leave comments or make code suggestions, and it even lets you copy an entire open-source project with a single click!

Since the code for my templates will remain on GitHub, I highly suggest checking in from time to time to see if the code has been updated. I won’t be posting a blog entry on pipwerks.com for every little edit I make to the code.

Speaking of edits, I made two or three tonight, spurred by an insightful comment from Jimmi Thøgersen.  He noticed a bug or two, and explained some of Saba’s bugginess — thanks Jimmi! If you know of any oddities or bugs, please let me know by posting an issue on GitHub.

Cleaning up Adobe Captivate’s SCORM Publishing Template, Part 7: Giving the Revisions a Home

I decided to post my revised Adobe Captivate publishing template to GitHub, where it can be easily copied, forked, and updated. Currently, the only files are for the Captivate 5.0 and 5.5 templates for SCORM 2004. I hope to add SCORM 1.2 soon, as well as replacing the default ‘standard.htm’ template, which doesn’t use any LMS-related code.

Update: The SCORM 1.2 template is now available.

If you take a look at Default.htm on GitHub, you’ll notice I’ve made a few changes since I wrote my series about editing the templates. I moved a few bits of markup/code around, added some configuration options (such as the ability to turn off centering, turn on logging, and require SCORM when loading), and added a ton of comments to explain some of the new options. Hopefully it’s all self-explanatory.

I also made a small edit to manifest2004.xml, and a few edits to scorm_support.js.

To use these template files, do the following:

  1. Make a backup of your entire publishing folder and put it somewhere safe!
  2. Go to Captivate’s Templates\Publish\SCORM\2004\ folder and replace Default.htm with the new file.
  3. Go to Templates\Publish\SCORM\2004\SCORM_support\ and replace scorm_support.js with the new file.
  4. While you’re in your SCORM_support folder, delete scorm_support.htm and scorm_support.swf, they won’t be used anymore.
  5. Go to Templates\Publish\ and replace manifest2004.xml with the new file.
  6. While you’re still in the Templates\Publish\ folder, replace standard.js with the new file.
  7. Restart Captivate and give it a try!

Find a bug? Think of a good edit for the template? Post a comment here, or better yet, file an issue on GitHub!