Skip to content

Commit 178c977

Browse files
authored
Merge pull request #68 from ben-xo/feature/itunes-season-tag
v1.38 option to output <itunes:season> from TPOS tag
2 parents cbdf706 + 0691d45 commit 178c977

13 files changed

+346
-68
lines changed

.github/workflows/testing.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ jobs:
44
build:
55
strategy:
66
matrix:
7-
operating-system: [ubuntu-latest, macos-10.15]
8-
php-versions: ['7.3', '7.4', '8.0', '8.1']
7+
operating-system: [ubuntu-latest, macos-11]
8+
php-versions: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
99
runs-on: ${{ matrix.operating-system }}
1010
steps:
1111
- name: Setup PHP and extensions
@@ -18,7 +18,7 @@ jobs:
1818
env:
1919
fail-fast: true
2020
- name: Checkout
21-
uses: actions/checkout@v2
21+
uses: actions/checkout@v3
2222
- name: Composer Install
2323
uses: ramsey/composer-install@v2
2424
with:

CHANGELOG.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
Changelog
22
=========
33

4+
1.38 2023-01-05 * Add <itunes:type> defaulting to "episodic". The ITUNES_TYPE
5+
option in dir2cast.ini will let you set the feed to
6+
<itunes:type>serial<itunes:type> set, and then,
7+
for each episode, the "Part Of A Set" tag (TPOS), if filled,
8+
will be used to output an <itunes:season> tag, and the
9+
"Track Number" tag (TRCK), if filled, will be used to output
10+
an <itunes:episode> tag. See the .ini for usage info.
11+
Suggested by @EdwarDDay (#65)
12+
* Fix deprecation warning in PHP 8.2, and tested up to PHP 8.3
13+
414
1.37 2022-10-27 * Errors now return an HTTP status code 500 by default.
515
* If the error is due to no content, or a bad URL passed to
616
?dir=, then it will be a 404 and no information about

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[![Testing dir2cast](https://github.com/ben-xo/dir2cast/actions/workflows/testing.yml/badge.svg)](https://github.com/ben-xo/dir2cast/actions/workflows/testing.yml)
22

33

4-
dir2cast by Ben XO v1.37 (2022-10-27)
4+
dir2cast by Ben XO v1.38 (2023-01-05)
55
================================================================================
66

77
https://github.com/ben-xo/dir2cast/

dir2cast.ini

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,19 @@
3333
; The full filesystem path to the MP3 folder
3434
; Set this if you *do not* want the folder to be passed in the URL.
3535
; (This defaults to the same folder as the script)
36-
;MP3_DIR = /home/ben_xo/public_html/my_mp3_folder
36+
;MP3_DIR = "/home/ben_xo/public_html/my_mp3_folder"
3737

3838
; The base to look for folders if they are specified in the URL
3939
; Set this if you *do* want the folder passed in the URL, but the passed
4040
; folders are not subfolders of where you installed dir2cast.php.
4141
; (This defaults to the same folder as the script)
42-
;MP3_BASE = /home/ben_xo/public_html/
42+
;MP3_BASE = "/home/ben_xo/public_html/"
4343

4444
; The URL of the MP3 folder
4545
; This defaults to the directory of the script.
4646
; dir2cast can usually work this out for you, but under some circumstances
4747
; it will fail. If your MP3 URLs are all wrong, try putting this in manually.
48-
;MP3_URL = http://www.example.foo/my_mp3_folder/
48+
;MP3_URL = "http://www.example.foo/my_mp3_folder/"
4949

5050
; Uncomment this if you want to check in every sub-folder for new files as well.
5151
;RECURSIVE_DIRECTORY_ITERATOR = true
@@ -70,7 +70,7 @@
7070

7171
; Email of the Author of the podcast for iTunes
7272
; This defaults to empty
73-
;ITUNES_OWNER_EMAIL = me-dir2cast@ben-xo.com
73+
;ITUNES_OWNER_EMAIL = "me-dir2cast@ben-xo.com"
7474

7575
; URL of the feed's home page (this is NOT where the MP3s are! It is
7676
; just the link to your "about" page).
@@ -101,7 +101,8 @@
101101
; See https://github.com/simplepie/simplepie-ng/wiki/Spec:-iTunes-Podcast-RSS
102102
; Valid values are "yes", "explicit", "true" or "no", "clean", "false"
103103
;
104-
; If you don't set this, it will not appear in the feed at all!
104+
; If you don't set this, it will not appear in the feed at all, and Apple
105+
; Podcasts may reject your feed!
105106
;ITUNES_EXPLICIT = "no"
106107

107108

@@ -157,16 +158,39 @@
157158
;
158159
;ITUNES_SUBTITLE_SUFFIX = ""
159160

161+
; Whether to output the <itunes:episode> and <itunes:season> tags
162+
;
163+
; Set the <itunes:type> tag. If you set this to "serial", it will
164+
; use the content of the ID3 "TRCK" and "TPOS" fields (also known as
165+
; "track number", and "part of a set" (or sometimes "disc number"),
166+
; respectively, as content of the <itunes:episode> and <itunes:season> tags.
167+
;
168+
; The TRCK and TPOS values should be non-zero positive integers (1, 2, 3, etc)
169+
;
170+
; NOTE: these are NOT used for ordering within the RSS feed. This is
171+
; still done by file date so it's up to you to make the episode order
172+
; and the file date order match by uploading them in season/episode order.
173+
;
174+
; The default value is "episodic", and no <itunes:episode> or <itunes:season>
175+
; tags will be output in the feed.
176+
;
177+
; If you don't want <itunes:type> to appear in the feed at all, set this to ""
178+
;
179+
; Any value that is not "" or "serial" will be treated as "episodic"
180+
;ITUNES_TYPE = "episodic"
181+
160182
; *** CHECK THESE ARE OK. ***
161183

162184
; Language of the feed
163185
; This defaults to en-us (US English). This must be something recognised by
164-
; the RSS standard.
165-
;LANGUAGE = en-us
186+
; the RSS standard. Apple Podcasts only supports values from the ISO 639 list
187+
; (two-letter language codes, with some possible modifiers, such as "en-us").
188+
; See https://www.loc.gov/standards/iso639-2/php/code_list.php
189+
;LANGUAGE = "en-us"
166190

167191
; Where to cache RSS feeds (this must be writable by the web server)
168192
; This defaults to a folder called 'temp' alongside the script
169-
;TMP_DIR = /tmp
193+
;TMP_DIR = "/tmp"
170194

171195
; Number of items to show in the feed
172196
; This defaults to 10
@@ -228,8 +252,8 @@
228252
; generated, or from a file with the same name with .txt extension) then
229253
; set this parameter to 'summary'. Otherwise it will get its description from
230254
; comment tag embedded in the file.
231-
;DESCRIPTION_SOURCE=comment
255+
;DESCRIPTION_SOURCE = "comment"
232256

233257
; If you want to have HTML in your <description> tag set this parameter.
234258
; Otherwise the content of the description will be escaped with htmlspecialchars()
235-
;DESCRIPTION_HTML=
259+
;DESCRIPTION_HTML =

dir2cast.php

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
/* DEFAULTS *********************************************/
5757

5858
// error handler needs these, so let's set them now.
59-
define('VERSION', '1.37');
59+
define('VERSION', '1.38');
6060
define('DIR2CAST_HOMEPAGE', 'https://github.com/ben-xo/dir2cast/');
6161
define('GENERATOR', 'dir2cast ' . VERSION . ' by Ben XO (' . DIR2CAST_HOMEPAGE . ')');
6262

@@ -173,7 +173,7 @@ public function appendToItem(DOMElement $d, DOMDocument $doc, RSS_Item $item)
173173
}
174174

175175
unset($this->getid3);
176-
176+
177177
if(!empty($info['comments']))
178178
{
179179
if(!empty($info['comments']['title'][0]))
@@ -184,6 +184,10 @@ public function appendToItem(DOMElement $d, DOMDocument $doc, RSS_Item $item)
184184
$item->setID3Album( $info['comments']['album'][0] );
185185
if(!empty($info['comments']['comment'][0]))
186186
$item->setID3Comment( $info['comments']['comment'][0] );
187+
if(!empty($info['comments']['track_number'][0]))
188+
$item->setID3Track( $info['comments']['track_number'][0] );
189+
if(!empty($info['comments']['part_of_a_set'][0]))
190+
$item->setID3PartOfASet( $info['comments']['part_of_a_set'][0] );
187191

188192
if(self::$AUTO_SAVE_COVER_ART)
189193
{
@@ -332,6 +336,7 @@ public function id()
332336
}
333337

334338
static $ITUNES_SUBTITLE_SUFFIX = '';
339+
static $ITUNES_TYPE = "episodic";
335340

336341
protected $owner_name, $owner_email, $image_href, $explicit;
337342
protected $categories = array();
@@ -391,6 +396,12 @@ public function appendToChannel(DOMElement $channel, DOMDocument $doc)
391396
$channel->appendChild( $doc->createElement('itunes:image') )
392397
->setAttribute('href', $this->image_href);
393398
}
399+
400+
if(strlen(iTunes_Podcast_Helper::$ITUNES_TYPE))
401+
{
402+
$channel->appendChild( $doc->createElement('itunes:type') )
403+
->appendChild( new DOMText( iTunes_Podcast_Helper::$ITUNES_TYPE == "serial" ? "serial" : "episodic" ) );
404+
}
394405
}
395406

396407
public function appendToItem(DOMElement $item_element, DOMDocument $doc, RSS_Item $item)
@@ -426,6 +437,22 @@ public function appendToItem(DOMElement $item_element, DOMDocument $doc, RSS_Ite
426437
{
427438
$elements['subtitle'] = $itunes_subtitle . iTunes_Podcast_Helper::$ITUNES_SUBTITLE_SUFFIX;
428439
}
440+
441+
if(iTunes_Podcast_Helper::$ITUNES_TYPE == "serial")
442+
{
443+
444+
$episode = $item->getEpisode();
445+
if($episode !== '')
446+
{
447+
$elements['episode'] = $episode;
448+
}
449+
450+
$season = $item->getSeason();
451+
if($season !== '')
452+
{
453+
$elements['season'] = $season;
454+
}
455+
}
429456

430457
foreach($elements as $key => $val)
431458
if(!empty($val))
@@ -919,6 +946,28 @@ public function getSubtitle()
919946
return $subtitle;
920947
}
921948

949+
public function getEpisode()
950+
{
951+
$episode = parent::getEpisode();
952+
if(!$episode)
953+
{
954+
// use track tag as season if there's no override
955+
$episode = $this->getID3Track();
956+
}
957+
return $episode;
958+
}
959+
960+
public function getSeason()
961+
{
962+
$season = parent::getSeason();
963+
if(!$season)
964+
{
965+
// use part_of_a_set tag as season if there's no override
966+
$season = $this->getID3PartOfASet();
967+
}
968+
return $season;
969+
}
970+
922971
/**
923972
* Version number used in the saved cache files. If the used fields change, increment this number.
924973
* @var integer
@@ -1956,6 +2005,9 @@ public static function defaults(array $SERVER)
19562005
if(!defined('ITUNES_SUBTITLE_SUFFIX'))
19572006
define('ITUNES_SUBTITLE_SUFFIX', '');
19582007

2008+
if(!defined('ITUNES_TYPE'))
2009+
define('ITUNES_TYPE', "episodic");
2010+
19592011
if(!defined('DESCRIPTION_SOURCE'))
19602012
define('DESCRIPTION_SOURCE', 'comment');
19612013

@@ -1986,6 +2038,7 @@ public static function defaults(array $SERVER)
19862038
Cached_Dir_Podcast::$MIN_CACHE_TIME = MIN_CACHE_TIME;
19872039
getID3_Podcast_Helper::$AUTO_SAVE_COVER_ART = AUTO_SAVE_COVER_ART;
19882040
iTunes_Podcast_Helper::$ITUNES_SUBTITLE_SUFFIX = ITUNES_SUBTITLE_SUFFIX;
2041+
iTunes_Podcast_Helper::$ITUNES_TYPE = ITUNES_TYPE;
19892042

19902043
// Set up up factory settings for RSS Items
19912044
RSS_File_Item::$FILES_URL = MP3_URL; // TODO: rename this to MEDIA_URL

test/ITunesPodcastSeasonTest.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php declare(strict_types=1);
2+
3+
use PHPUnit\Framework\TestCase;
4+
5+
class ITunesPodcastSeasonTest extends MixedMediaExampleTest
6+
{
7+
public static function setUpBeforeClass(): void
8+
{
9+
prepare_testing_dir();
10+
11+
copy('../fixtures/id3v1_artist_album_title.mp3', '1.mp3');
12+
copy('../fixtures/id3v2_artist_album_title.mp3', '2.mp3');
13+
copy('../fixtures/tagged.mp4', '3.mp4');
14+
copy('../fixtures/id3v2_comment.mp3', '4.mp3');
15+
copy('../fixtures/id3v2_artist_title_partofaset.mp3', '5.mp3');
16+
copy('../fixtures/id3v2_artist_title_track.mp3', '6.mp3');
17+
18+
$now = time();
19+
touch('1.mp3', $now);
20+
touch('2.mp3', $now+50);
21+
touch('3.mp4', $now+100);
22+
touch('4.mp3', $now+150);
23+
touch('5.mp3', $now+200);
24+
touch('6.mp3', $now+250);
25+
MixedMediaExampleTest::$filemtime = $now;
26+
27+
file_put_contents('./dir2cast.ini', "ITUNES_TYPE = serial\n");
28+
29+
MixedMediaExampleTest::$output = '';
30+
exec('php dir2cast.php --media-url=https://www.example.com/podcast/ --output=out.xml --min-file-age=0', MixedMediaExampleTest::$output, MixedMediaExampleTest::$returncode);
31+
}
32+
33+
public function test_itunes_type()
34+
{
35+
// generated valid XML
36+
$data = simplexml_load_string(file_get_contents(self::$file));
37+
$itdtd = "http://www.itunes.com/dtds/podcast-1.0.dtd";
38+
39+
// assert itunes:type = serial
40+
$this->assertEquals('serial', $data->channel->children($itdtd)->type);
41+
}
42+
43+
public function test_itunes_season()
44+
{
45+
// generated valid XML
46+
$data = simplexml_load_string(file_get_contents(self::$file));
47+
$itdtd = "http://www.itunes.com/dtds/podcast-1.0.dtd";
48+
$this->assertEmpty($data->channel->item[0]->children($itdtd)->season);
49+
$this->assertEquals('Season 1', $data->channel->item[1]->children($itdtd)->season);
50+
$this->assertEmpty($data->channel->item[2]->children($itdtd)->season);
51+
$this->assertEmpty($data->channel->item[3]->children($itdtd)->season);
52+
$this->assertEmpty($data->channel->item[4]->children($itdtd)->season);
53+
$this->assertEmpty($data->channel->item[5]->children($itdtd)->season);
54+
}
55+
56+
public function test_itunes_episode()
57+
{
58+
// generated valid XML
59+
$data = simplexml_load_string(file_get_contents(self::$file));
60+
$itdtd = "http://www.itunes.com/dtds/podcast-1.0.dtd";
61+
$this->assertEquals('1', $data->channel->item[0]->children($itdtd)->episode);
62+
$this->assertEmpty($data->channel->item[1]->children($itdtd)->episode);
63+
$this->assertEmpty($data->channel->item[2]->children($itdtd)->episode);
64+
$this->assertEmpty($data->channel->item[3]->children($itdtd)->episode);
65+
$this->assertEmpty($data->channel->item[4]->children($itdtd)->episode);
66+
$this->assertEmpty($data->channel->item[5]->children($itdtd)->episode);
67+
}
68+
69+
70+
}

test/Media_RSS_ItemTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ public function getID3Comment()
5050
return '';
5151
}
5252

53+
public function getID3PartOfASet()
54+
{
55+
return '';
56+
}
57+
5358
protected $media_rss_item_class = 'Media_RSS_Item';
5459

5560
public function newRSSItem()
@@ -67,6 +72,7 @@ public function newRSSItem()
6772
$item->setID3Title($this->getID3Title());
6873
$item->setID3Artist($this->getID3Artist());
6974
$item->setID3Comment($this->getID3Comment());
75+
$item->setID3PartOfASet($this->getID3PartOfASet());
7076
return $item;
7177
}
7278

@@ -136,6 +142,11 @@ public function test_summary_from_description_when_summary_not_set_and_descripti
136142
$this->assertEquals('', $item->getSummary());
137143
}
138144

145+
public function test_season_from_part_of_set_tag_by_default() {
146+
$item = $this->newRSSItem();
147+
$this->assertEquals($this->getID3PartOfASet(), $item->getSeason());
148+
}
149+
139150
public function tearDown(): void
140151
{
141152
file_exists($this->filename) && unlink($this->filename);

0 commit comments

Comments
 (0)