diff --git a/backends/sql/mysql/specifics.php b/backends/sql/mysql/specifics.php index 42589007..36604f8d 100644 --- a/backends/sql/mysql/specifics.php +++ b/backends/sql/mysql/specifics.php @@ -183,6 +183,7 @@ function check_sql_tables() { "Description TEXT, ". "LastPubDate INT UNSIGNED DEFAULT NULL, ". "Category VARCHAR(255) NOT NULL, ". + "WriteTags TINYINT(1) DEFAULT 0, ". "PRIMARY KEY (PODindex)) ENGINE=InnoDB", true)) { logger::log("MYSQL", " Podcasttable OK"); @@ -896,6 +897,12 @@ function check_sql_tables() { generic_sql_query("UPDATE Statstable SET Value = 66 WHERE Item = 'SchemaVer'", true); break; + case 66: + logger::log("SQL", "Updating FROM Schema version 66 TO Schema version 67"); + generic_sql_query("ALTER TABLE Podcasttable ADD WriteTags TINYINT(1) DEFAULT 0", true); + generic_sql_query("UPDATE Statstable SET Value = 67 WHERE Item = 'SchemaVer'", true); + break; + } $sv++; } diff --git a/backends/sql/sqlite/specifics.php b/backends/sql/sqlite/specifics.php index 84ce1588..493547f2 100644 --- a/backends/sql/sqlite/specifics.php +++ b/backends/sql/sqlite/specifics.php @@ -869,6 +869,12 @@ function check_sql_tables() { generic_sql_query("UPDATE Statstable SET Value = 66 WHERE Item = 'SchemaVer'", true); break; + case 66: + logger::log("SQL", "Updating FROM Schema version 66 TO Schema version 67"); + generic_sql_query("ALTER TABLE Podcasttable ADD COLUMN WriteTags TINYINT(1) DEFAULT 0", true); + generic_sql_query("UPDATE Statstable SET Value = 67 WHERE Item = 'SchemaVer'", true); + break; + } $sv++; } diff --git a/docs/Podcasts.md b/docs/Podcasts.md index 3c9dcda8..54dfad24 100644 --- a/docs/Podcasts.md +++ b/docs/Podcasts.md @@ -69,7 +69,7 @@ The search box below the controls will search that Podcast's episodes for the te Each Podcast has several configuration options which are stored alongside the podcast. -![](images/podcasts2.png) +![](images/podcasts6.png) * **Display:** You can choose to display Everything, only New Episodes, only New and Unlistened episodes, only New and Downloaded Episodes, or only Downloaded episodes. * **Refresh:** Automatically refresh the podcast at the specified interval. This will be set automatically if the podcast feed provides that information. @@ -78,6 +78,7 @@ Each Podcast has several configuration options which are stored alongside the po * **Sort Order:** Either Newest first or Oldest first * **Keep all downloaded episodes:** Downloaded episodes will not count towards 'Number To Keep' and will not be removed if they become older than 'Keep Episodes For'. * **Hide Descriptions:** By default, don't show the description for each episode. You can still expand the descriptions by clicking the triangle. +* **Write ID3 Tags to Downloaded Files:** A lot of us like to download podcasts to listen to offline. I particularly like to copy them to a USB stick to play on my car stereo when I'm driving, as I frequently do, through areas of remote countryside with no radio or phone reception. A few podcasts (I'm looking at you, Welcome To Night Vale, though you're not the only one but it's ironic how I can't listen to you when I'm in remote areas) do not ID3 tag their files, which confuses the living doodoo out of my car stereo and makes everything play in the wrong order. Selecting this option will tag downloaded files with artist, albumartist, album, and title tags, as well as filling in the track number if it doesn't exist with something which will keep the tracks in order. You can set default values for some of these options, to be set on newly subscribed podcasts, from the Configuration Panel. Note that some podcasts announce their own refresh period. In this case that value will override your default. diff --git a/docs/images/podcasts2.png b/docs/images/podcasts2.png deleted file mode 100644 index 6797fba4..00000000 Binary files a/docs/images/podcasts2.png and /dev/null differ diff --git a/docs/images/podcasts6.png b/docs/images/podcasts6.png new file mode 100644 index 00000000..ec9cc938 Binary files /dev/null and b/docs/images/podcasts6.png differ diff --git a/includes/vars.php b/includes/vars.php index 4a19b307..85aabe0c 100644 --- a/includes/vars.php +++ b/includes/vars.php @@ -8,8 +8,8 @@ define('ROMPR_MAX_TRACKS_PER_TRANSACTION', 500); define('ROMPR_COLLECTION_VERSION', 6); define('ROMPR_IMAGE_VERSION', 4); -define('ROMPR_SCHEMA_VERSION', 66); -define('ROMPR_VERSION', '1.43'); +define('ROMPR_SCHEMA_VERSION', 67); +define('ROMPR_VERSION', '1.46'); define('ROMPR_IDSTRING', 'RompR Music Player '.ROMPR_VERSION); define('ROMPR_MOPIDY_MIN_VERSION', 1.1); define('ROMPR_UNKNOWN_STREAM', "Unknown Internet Stream"); diff --git a/international/en.php b/international/en.php index 9b704b45..2234840b 100644 --- a/international/en.php +++ b/international/en.php @@ -704,8 +704,9 @@ "label_tracktitle" => "Track Title", "label_albumtitle" => "Album Title", "label_tracknumber" => "Track Number", - "config_use_original_releasedate" => "Use Original Release Date for Album Date, if present", + "config_use_original_releasedate" => "Use Original Release Date for Album Date, if present", "label_cantataimporter" => "Import Ratings from Cantata", + "podcast_writetags" => "Write ID3 Tags to Downloaded Files" // ---------------------------------------------------- // Proobaly Unused // ---------------------------------------------------- diff --git a/player/mpd/controller.js b/player/mpd/controller.js index 4ff1edad..0139fa96 100644 --- a/player/mpd/controller.js +++ b/player/mpd/controller.js @@ -385,6 +385,11 @@ function playerController() { self.do_command_list([["seek", player.status.song, parseInt(seekto.toString())]]); } + this.seekcur = function(seekto) { + debug.log("PLAYER","Seeking Current To",seekto); + self.do_command_list([["seekcur", seekto.toString()]]); + } + this.playId = function(id) { playlist.checkPodcastProgress(); self.do_command_list([["playid",id]]); diff --git a/player/player.js b/player/player.js index 3390734a..0b7ae1da 100644 --- a/player/player.js +++ b/player/player.js @@ -200,10 +200,7 @@ var player = function() { skip: function(sec) { if (this.status.state == "play") { - var p = player.status.progress ? 0 : player.status.progress; - var to = p + sec; - if (p < 0) p = 0; - this.controller.seek(to); + this.controller.seekcur(sec > 0 ? "+"+sec : sec); } } diff --git a/podcasts/podcastfunctions.php b/podcasts/podcastfunctions.php index ea2948ee..07f0f9e7 100644 --- a/podcasts/podcastfunctions.php +++ b/podcasts/podcastfunctions.php @@ -1,5 +1,7 @@ '; + print '
'; + print 'WriteTags == 1) { + print ' checked'; + } + print '>
'; + print ''; print ''; @@ -1101,11 +1111,24 @@ function downloadTrack($key, $channel) { logger::mark("PODCASTS", "Downloading track",$key,"from podcast",$channel); $url = null; $filesize = 0; - $result = generic_sql_query("SELECT Link, FileSize FROM PodcastTracktable WHERE PODTrackindex = " . intval($key), false, PDO::FETCH_OBJ); + $result = generic_sql_query(" + SELECT Link, + FileSize, + WriteTags, + Duration, + Podcasttable.Title AS album, + Podcasttable.Artist AS artist, + PodcastTracktable.Title AS title + FROM PodcastTracktable + JOIN Podcasttable USING (PODindex) + WHERE PODTrackindex = " . intval($key), false, PDO::FETCH_OBJ); foreach ($result as $obj) { $url = $obj->Link; $filesize = $obj->FileSize; } + logger::log("PODCASTS", " Artist is",$obj->artist); + logger::log("PODCASTS", " Album is",$obj->album); + logger::log("PODCASTS", " Title is",$obj->title); if ($url === null) { logger::warn("PODCASTS", " Failed to find URL for podcast",$channel); header('HTTP/1.0 404 Not Found'); @@ -1131,8 +1154,54 @@ function downloadTrack($key, $channel) { } logger::trace("PODCASTS", "Downloading To prefs/podcasts/".$channel.'/'.$key.'/'.$filename); $d = new url_downloader(array('url' => $url)); - if ($d->get_data_to_file('prefs/podcasts/'.$channel.'/'.$key.'/'.$filename, true)) { - sql_prepare_query(true, null, null, null, "UPDATE PodcastTracktable SET Downloaded=?, Localfilename=? WHERE PODTrackindex=?", 1, '/prefs/podcasts/'.$channel.'/'.$key.'/'.$filename, $key); + $download_file = 'prefs/podcasts/'.$channel.'/'.$key.'/'.$filename; + if ($d->get_data_to_file($download_file, true)) { + if ($obj->WriteTags != 0) { + logger::log('PODCASTS', 'Writing Tags to',$download_file); + $getID3 = new getID3; + $getID3->setOption(array('encoding'=>'UTF-8')); + + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.php', __FILE__, true); + + $tagwriter = new getid3_writetags; + $tagwriter->filename = $download_file; + $tagwriter->tagformats = array('id3v2.4'); + $tagwriter->overwrite_tags = true; + $tagwriter->tag_encoding = 'UTF-8'; + $tagwriter->remove_other_tags = false; + $tags = array( + 'artist' => array($obj->artist), + 'albumartist' => array($obj->artist), + 'album' => array($obj->album), + 'title' => array($obj->title) + ); + $tracknumber = format_tracknum($obj->title); + if ($tracknumber > 0) { + $tags['tracknumber'] = array($tracknumber); + } + $tagwriter->tag_data = $tags; + if ($tagwriter->WriteTags()) { + logger::log('PODCASTS', 'Successfully wrote tags'); + if (!empty($tagwriter->warnings)) { + logger::log('There were some warnings'.implode(' ', $tagwriter->warnings)); + } + } else { + logger::error('PODCASTS', 'Failed to write tags!', implode(' ', $tagwriter->errors)); + } + } else { + logger::log('PODCASTS', 'Not writing tags'); + } + if ($obj->Duration == 0 || $obj->Duration === null) { + logger::log('PODCASTS', 'Trying to work out duration'); + $getID3 = new getID3; + $file = $getID3->analyze($download_file); + $p = $file['playtime_seconds']; + if ($p) { + $obj->Duration = $p; + logger::log('PODCASTS', 'Duration is',$p,'seconds'); + } + } + sql_prepare_query(true, null, null, null, "UPDATE PodcastTracktable SET Duration = ?, Downloaded=?, Localfilename=? WHERE PODTrackindex=?", $obj->Duration, 1, '/'.$download_file, $key); } else { logger::error('PODCASTS', 'Failed to download',$key, $channel, $url); header('HTTP/1.0 404 Not Found'); diff --git a/streamplugins/04_communityradio.php b/streamplugins/04_communityradio.php index e3d65a3c..7cd4d1a5 100644 --- a/streamplugins/04_communityradio.php +++ b/streamplugins/04_communityradio.php @@ -26,6 +26,9 @@ public function __construct() { } public function doWhatYoureTold() { + + $this->server = $this->get_server(); + switch ($this->populate) { case 0: $this->doHeader(); @@ -214,7 +217,7 @@ private function doGenreList() { private function browse() { directoryControlHeader('commradio_'.md5($this->url), $this->title); - $bits = getCacheData('http://www.radio-browser.info/webservice/'.$this->url, 'commradio', true, true); + $bits = getCacheData('https://'.$this->server.'/'.$this->url, 'commradio', true, true); $bits = json_decode($bits, true); if ($this->url == 'json/countries') { $map = 'bycountryexact/'; @@ -227,7 +230,7 @@ private function browse() { } private function doSearch() { - $url = 'http://www.radio-browser.info/webservice/json/stations/search?'; + $url = 'https://'.$this->server.'/json/stations/search?'; $ourterms = array(); foreach ($this->searchterms as $t) { if (array_key_exists($t, $_REQUEST) && $_REQUEST[$t] != '') { @@ -256,7 +259,8 @@ private function addBits($url) { } private function doRequest() { - $url = $this->addBits('http://www.radio-browser.info/webservice/json/stations/'.$this->url.'?'); + $url = $this->addBits('https://'.$this->server.'/json/stations/'.$this->url.'?'); + logger::log('COMMRADIO','Getting',$url); $stations = getCacheData($url, 'commradio', true, true); $stations = json_decode($stations, true); $title = ($this->title) ? rawurldecode($this->title) : get_int_text('label_communityradio'); @@ -317,10 +321,10 @@ private function doStation($station, $index) { private function makeSelector($json, $root) { if (is_array($json)) { foreach ($json as $thing) { - $val = strtolower($thing['value']); + $val = strtolower($thing['name']); $opts = array( 'URL' => $root.rawurlencode($val), - 'text' => ucfirst($thing['value']).' ('.$thing['stationcount'].' stations)' + 'text' => ucfirst($thing['name']).' ('.$thing['stationcount'].' stations)' ); printRadioDirectory($opts, true, 'commradio'); } @@ -407,16 +411,24 @@ private function comm_radio_sanitise_station($station) { if ($result['bitrate'] == 0) { $result['bitrate'] = 'Unknown '; } - // No real idea whay one works for one player but not the other. MPD won't load the M3U files, - // Mopidy won't load the PLS files. All I do is send a load/add and a URL..... - if ($prefs['player_backend'] == 'mpd') { - $result['playurl'] = 'http://www.radio-browser.info/webservice/v2/pls/url/'.$station['id']; - } else { - $result['playurl'] = 'http://www.radio-browser.info/webservice/v2/m3u/url/'.$station['id']; - } + $result['playurl'] = $station['url']; return $result; } + private function get_server() { + $servers = dns_get_record('all.api.radio-browser.info'); + shuffle($servers); + foreach ($servers as $server) { + if (array_key_exists('ip', $server)) { + $name = gethostbyaddr($server['ip']); + logger::log('COMMRADIO', 'Using server',$name); + return $name; + } + } + logger::warn('COMMRADIO', 'Using fallback server!'); + return 'de1.api.radio-browser.info'; + } + }