You need to set the global
$post object to your post. This is done by the loop functions in your theme which is why it works there.
WP's oEmbed replaces the URLs during the
the_content filter. But it replaces the URLs through two means. The first is a simple regex find and replace for certain URLs such as Google Video or linking directly to a file with a video format extension.
The second way oEmbed handles it is the way most services (such as YouTube) are handled. The second way is similar to the first except that the results are stored in the
postmeta table which is why we need to set the
$post. In the second way, the various services handlers are asked if a URL is theirs. If the URL is theirs, they will generate the HTML for the embed. Then oEmbed caches the HTML for the URL into the
postmeta table. The reason for this is because a services may require a remote API call to get the HTML code, and we would like to minimize this overhead each time the post is displayed.
$thePost = get_post(42);
// Set our post to the globally used $post object as oEmbed will use it to do a meta value lookup
$post = $thePost;
echo apply_filters('the_content', $thePost->post_content );