Skip to content

Instantly share code, notes, and snippets.

@mailinglists35
Created August 31, 2022 14:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mailinglists35/9f799157fe83131873aae9813841d8ce to your computer and use it in GitHub Desktop.
Save mailinglists35/9f799157fe83131873aae9813841d8ce to your computer and use it in GitHub Desktop.
<?php
// Settings.
$feedName = 'Opus codec';
$feedDesc = 'Opus codec';
$feedURL = 'https://example.com/podcast/';
$coverImageFileName = 'audiobooks.png';
$ffprobePath = '/usr/bin/ffprobe';
$ffmpegPath = '/usr/bin/ffmpeg';
function outputRSS($books) {
global $feedName;
global $feedDesc;
global $feedURL;
global $coverImageFileName;
setlocale(LC_CTYPE, 'en_US.UTF-8');
header('Content-type: text/xml');
echo '<?xml version="1.0"?>' . "\n";
echo '<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">' . "\n";
echo ' <channel>' . "\n";
// add this to not be indexed in apple/google
// echo ' <itunes:block>yes</itunes:block>' . "\n";
// add this to restrict further to not be able to be added in native ios/android podcast apps
// echo ' <googleplay:block>yes</googleplay:block>' . "\n";
echo ' <title>' . htmlspecialchars($feedName) . '</title>' . "\n";
echo ' <link>' . htmlspecialchars($feedURL) . '</link>' . "\n";
echo ' <description>' . htmlspecialchars($feedDesc) . '</description>' . "\n";
echo ' <image>' . "\n";
echo ' <url>' . htmlspecialchars($feedURL . $coverImageFileName) . '</url>' . "\n";
echo ' <title>' . htmlspecialchars($feedName) . '</title>' . "\n";
echo ' <link>' . htmlspecialchars($feedURL) . '</link>' . "\n";
echo ' </image>' . "\n";
foreach($books as $book_filename => $book_details) {
echo ' <item>' . "\n";
echo ' <title>' . htmlspecialchars($book_details['title']) . '</title>' . "\n";
echo ' <itunes:title>' . htmlspecialchars($book_details['title']) . '</itunes:title>' . "\n";
echo ' <description>' . htmlspecialchars($book_details['description']) . '</description>' . "\n";
echo ' <author>' . htmlspecialchars($book_details['author']) . '</author>' . "\n";
echo ' <pubDate>' . htmlspecialchars($book_details['published_date']) . '</pubDate>' . "\n";
echo ' <link>' . htmlspecialchars($book_details['url']) . '</link>' . "\n";
echo ' <guid>' . htmlspecialchars($book_details['url']) . '</guid>' . "\n";
echo ' <enclosure url="' . htmlspecialchars($book_details['url']) . '" ' .
'length="' . htmlspecialchars($book_details['size']) . '" ' .
'type="audio/mpeg" />' . "\n";
if (isset($book_details['cover'])) {
echo ' <itunes:image href="' . htmlspecialchars($book_details['cover']) . '" />' . "\n";
}
echo ' </item>' . "\n";
}
echo ' </channel>' . "\n";
echo '</rss>' . "\n";
}
function main() {
global $feedURL;
global $ffprobePath;
global $ffmpegPath;
// Read the cache file, if it exists.
$cache_file = '/tmp/.cache.opus.json';
$cached_book_details = array();
if (file_exists($cache_file)) {
$cached_book_details = json_decode(file_get_contents($cache_file), 2);
}
// Loop through each book (mp3) file.
$book_details = array();
foreach (glob('*24k*.opus') as $book_file) {
$size = filesize($book_file);
// Reuse cached book details if available and the file size hasn't changed.
if (isset($cached_book_details[$book_file]) &&
isset($cached_book_details[$book_file]['size']) &&
$cached_book_details[$book_file]['size'] == $size) {
$book_details[$book_file] = $cached_book_details[$book_file];
continue;
}
// Book details aren't already cached, so figure them out now.
$details = array();
// Read the book's ID3 tags (if any).
$id3_tags = json_decode(shell_exec(
escapeshellarg($ffprobePath) . ' ' .
'-print_format json -show_entries stream=codec_name:format ' .
'-select_streams a:0 -v quiet ' .
escapeshellarg($book_file)
), true)['format']['tags'];
// Take note of the book's title.
if (isset($id3_tags['title'])) {
$details['title'] = $id3_tags['title'];
} else {
$details['title'] = preg_replace('/\.opus$/', '', $book_file);
}
// Take note of the book's author.
if (isset($id3_tags['artist'])) {
$details['author'] = $id3_tags['artist'];
} else if (isset($id3_tags['composer'])) {
$details['author'] = $id3_tags['composer'];
} else {
$details['author'] = 'Unknown';
}
// Take note of the book's description.
// Note: Add extra newlines so that the description is easier to read in
// mobile podcast apps.
if (isset($id3_tags['description'])) {
$details['description'] = preg_replace("/\n+/", "\n\n",
$id3_tags['description']);
} else {
$details['description'] = '';
}
// Append the author and narrator details to the description.
$details['description'] .= "\n\n" . 'Author: ' . $details['author'] . "\n";
if (isset($id3_tags['narrated_by'])) {
$details['description'] .= 'Narrator: ' . $id3_tags['narrated_by'];
}
// Fetch the cover art (if any is available) from the file if the image is
// not yet saved.
$cover_file = preg_replace('/\.mp3$/', '.jpg', $book_file);
if (file_exists($cover_file) ||
shell_exec(
escapeshellarg($ffmpegPath) . ' -loglevel quiet -i ' .
escapeshellarg($book_file) . ' -vcodec copy ' .
escapeshellarg($cover_file)
)) {
$details['cover'] = $feedURL . rawurlencode($cover_file);
}
// Take note of when the book was published.
if (isset($id3_tags['year'])) {
$details['published_date'] = date(DATE_RSS, strtotime($id3_tags['year']));
} else {
$details['published_date'] = date(DATE_RSS, filemtime($book_file));
}
// Take note of the book's other details.
$details['url'] = $feedURL . rawurlencode($book_file);
$details['size'] = $size;
// Save the book's details to the associative array.
$book_details[$book_file] = $details;
}
// Write all the book details to the cache file to save work next time.
file_put_contents($cache_file, json_encode($book_details));
// Finally, output the RSS!
outputRSS($book_details);
}
// Ensure the feed URL ends with a '/' and that the ffmpeg paths look correct.
// Hopefully that should make configuration less error-prone.
if (substr($feedURL, -1) != '/') {
$feedURL .= '/';
}
if (!file_exists($ffprobePath) || !file_exists($ffmpegPath)) {
fwrite(STDERR, 'Incorrect $ffprobePath or $ffmpegPath.' . PHP_EOL);
exit(1);
}
main();
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment