1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
|
MediaTomb Scripting
This documentation is valid for MediaTomb version 0.12.1.
Copyright © 2005 Gena Batsyan, Sergey Bostandzhyan
Copyright © 2006-2010 Gena Batsyan, Sergey Bostandzhyan,
Leonhard Wimmer
THIS SOFTWARE COMES WITH ABSOLUTELY NO WARRANTY! USE AT YOUR
OWN RISK!
__________________________________________________________
Table of Contents
1. Introduction
2. How It Works
2.1. Understanding Virtual Objects.
2.2. Theory Of Operation
3. Global Variables And Constants
3.1. The Media Object
3.2. Constants
4. Functions
4.1. Native Server Functions
4.2. Helper Functions
5. Walkthrough
5.1. Import Script
5.2. Playlist Script
5.3. DVD Import Script
1. Introduction
MediaTomb allows you to customize the structure of how your
media is being presented to your renderer. One of the most
important features introduced since the version 0.8 are the
virtual containers and virtual items. Let's think of possible
scenarios:
* you may want to separate your content by music, photo,
video, maybe create a special container with all non
playable stuff
* you may want your music to be sorted by genre, year,
artist, album, or maybe by starting letters, so you can
more easily find your favorite song when browsing the
server
* you want to have your photos that you took with your
favorite digital camera to appear in a special folder, or
maybe you even want to separate the photos that you took
with flash-on from the ones that you made without flash
* your media player does not support video, so you do not
even want to see the Video container
* it's up to your imagination :)
The scenarios described above and much more can be achieved
with the help of an import script.
Version 0.10.0 introduces a playlist parsing feature, which is
also handled by scripting and version 0.12.0 adds a script for
creating a virtual layout out of a DVD iso image.
2. How It Works
This section will give you some overview on how virtual objects
work and on how they are related to scripting.
NOTE:
In order to use the import scripting feature you have to
change the layout type from builtin to js in config.xml
!
NOTE:
The sorting of Video and Photo items using the
“rootpath” object is still somewhat experimental and not
described here.
2.1. Understanding Virtual Objects.
When you add a file or directory to the database via the web
interface several things happen.
1. The object is inserted into the PC Directory. PC Directory
is simply a special non-removable container. Any media file
added will have an entry inside the PC Directory tree. PC
Directory's hierarchy reflects the file system hierarchy,
all objects inside the PC Directory including itself are
NON-VIRTUAL objects. All virtual objects may have a
different title, description, etc., but they are still
references to objects in the PC-Directory. That's why it is
not possible to change a location of a virtual object - the
only exceptions are URL items and Active items.
2. Once an item is added to the PC Directory it is forwarded
to the virtual object engine. The virtual object engine's
mission is to organize and present the media database in a
logical hierarchy based on the available metadata of the
items.
Each UPnP server implements this so called virtual object
hierarchy in a different way. Audio files are usually sorted by
artist, album, some servers may just present a view similar to
the file system and so on. Most servers have strong limitations
on the structure of the virtual containers, they usually offer
a predefined layout of data and the user has to live with it.
In MediaTomb we try to address this shortcoming by introducing
the scriptable virtual object engine. It is designed to be:
* maximally flexible
* easily customizable and extendable
* robust and efficient
We try to achieve these goals by embedding a scripting runtime
environment that allows the execution of ECMAScript-262 conform
scripts better known as JavaScript. We are using Mozilla's
JavaScript implementation called SpiderMonkey, it is a
stand-alone easily embeddable javascript engine, supporting
JavaScript versions 1.0 through 1.4.
2.2. Theory Of Operation
After an item is added to the PC Directory it is automatically
fed as input to the import script. The script then creates one
or more virtual items for the given original item. Items
created from scripts are always marked virtual.
When the virtual object engine gets notified of an added item,
following happens: a javascript object is created mirroring the
properties of the item. The object is introduced to the script
environment and bound to the predefined variable 'orig'. This
way a variable orig is always defined for every script
invocation and represents the original data of the added item.
Then the script is invoked.
In the current implementation, if you modify the script then
you will have to restart the server for the new logic to take
effect. Note, that the script is only triggered when new
objects are added to the database, also note that the script
does not modify any objects that already exist in the database
- it only processes new objects that are being added. When a
playlist item is encountered, it is automatically fed as input
to the playlist script. The playlist script attempts to parse
the playlist and adds new item to the database, the item is
then processed by the import script.
3. Global Variables And Constants
In this section we will introduce the properties of the object
that will be processed by the script, as well as functions that
are offered by the server.
3.1. The Media Object
As described in Section 2.2, each time an item is added to the
database the import script is invoked. So, one script
invocation processes exactly one non virtual item, and creates
a number of virtual items and containers. The original item is
made available in the form of the global variable 'orig'.
Additionally, when the object being imported is a playlist, it
is made available to the playlist parser script in the form of
the global variable 'playlist'. It is usually a good idea to
only read from these variables and to create and only modify
local copies.
Note:
modifying the properties of the orig object will not
propagate the changes to the database, only a call to
the addCdsObject() will permanently add the object.
3.1.1. General Properties
Here is a list of properties of an object, you can set them you
create a new object or when you modify a copy of the 'orig'
object.
RW means read/write, i.e. - changes made to that property will
be transferred into the database.
RO means, that this is a read only property, any changes made
to it will get lost.
*
orig.objectType
RW
This defines the object type, following types are
available:
+
OBJECT_TYPE_CONTAINER
Object is a container.
+
OBJECT_TYPE_ITEM
Object is an item.
+
OBJECT_TYPE_ACTIVE_ITEM
Object is an active item.
+
OBJECT_TYPE_ITEM_EXTERNAL_URL
Object is a link to a resource on the Internet.
+
OBJECT_TYPE_ITEM_INTERNAL_URL
Object is an internal link.
*
orig.title
RW
This is the title of the original object, since the object
represents an entry in the PC-Directory, the title will be
set to it's file name. This field corresponds to dc:title
in the DIDL-Lite XML.
*
orig.id
RO
The object ID, make sure to set all refID's (reference IDs)
of your virtual objects to that ID.
*
orig.parentID
RO
The object ID of the parent container.
*
orig.upnpclass
RW
The UPnP class of the item, this corresponds to upnp:class
in the DIDL-Lite XML.
*
orig.location
RO
Location on disk, given by the absolute path and file name.
*
orig.theora
RO
This property is a boolean value, it is non zero if the
particular item is of type OGG Theora. This is useful to
allow proper sorting of media and thus placing OGG Vorbis
into the Audio container and OGG Theora into the Video
container.
*
orig.onlineservice
RO
Identifies if the item belongs to an online service and
thus has extended properties. Following types are
available:
+
ONLINE_SERVICE_NONE
The item does not belong to an online service and does
not have extended properties.
+
ONLINE_SERVICE_YOUTUBE
The item belongs to the YouTube service and has
extended properties.
+
ONLINE_SERVICE_WEBORAMA
The item belongs to the Weborama service and has
extended properties.
+
ONLINE_SERVICE_APPLE_TRAILERS
The item belongs to the Apple Trailers service and has
extended properties.
*
orig.mimetype
RW
Mimetype of the object.
*
orig.meta
RW
Array holding the metadata that was extracted from the
object (i.e. id3/exif/etc. information)
+
orig.meta[M_TITLE]
RW
Extracted title (for example the id3 title if the
object is an mp3 file), if you want that your new
virtual object is displayed under this title you will
have to set obj.title = orig.meta[M_TITLE]
+
orig.meta[M_ARTIST]
RW
Artist information, this corresponds to upnp:artist in
the DIDL-Lite XML.
+
orig.meta[M_ALBUM]
RW
Album information, this corresponds to upnp:album in
the DIDL-Lite XML.
+
orig.meta[M_DATE]
RW
Date, must be in the format of YYYY-MM-DD (required by
the UPnP spec), this corresponds to dc:date in the
DIDL-Lite XML.
+
orig.meta[M_GENRE]
RW
Genre of the item, this corresponds to upnp:genre in
the DIDL-Lite XML.
+
orig.meta[M_DESCRIPTION]
RW
Description of the item, this corresponds to
dc:description in the DIDL-Lite XML.
+
orig.meta[M_REGION]
RW
Region description of the item, this corresponds to
upnp:region in the DIDL-Lite XML.
+
orig.meta[M_TRACKNUMBER]
RW
Track number of the item, this corresponds to
upnp:originalTrackNumber in the DIDL-Lite XML.
+
orig.meta[M_AUTHOR]
RW
Author of the media, this corresponds to upnp:author
in the DIDL-Lite XML.
+
orig.meta[M_DIRECTOR]
RW
Director of the media, this corresponds to
upnp:director in the DIDL-Lite XML.
+
orig.meta[M_PUBLISHER]
RW
Director of the media, this corresponds to
dc:publisher in the DIDL-Lite XML.
+
orig.meta[M_RATING]
RW
Director of the media, this corresponds to upnp:rating
in the DIDL-Lite XML.
+
orig.meta[M_ACTOR]
RW
Director of the media, this corresponds to upnp:actor
in the DIDL-Lite XML.
+
orig.meta[M_PRODUCER]
RW
Director of the media, this corresponds to
upnp:producer in the DIDL-Lite XML.
*
orig.aux
RO
Array holding the so called auxiliary data. Aux data is
metadata that is not part of UPnP, for example - this can
be a camera model that was used to make a photo, or the
information if the photo was taken with or without flash.
Currently aux data can be gathered from libexif and
libextractor (see the Import section in the main
documentation for more details). So, this array will hold
the tags that you specified in your config.xml, allowing
you to create your virtual structure according to your
liking.
*
orig.playlistOrder
RW
This property is only available if the object is being
created by the playlist script. It's similar to ID3 track
number, but is used to set the position of the newly
created object inside a parsed playlist container. Usually
you will increment the number for each new object that you
create while parsing the playlist, thus ensuring that the
resulting order is the same as in the original playlist.
3.1.2. YouTube Properties
When the obj.onlineservice variable equals
ONLINE_SERVICE_YOUTUBE the item has the following additional
properties:
*
orig.yt_request
RO
Identifies the YouTube request type, following types are
available:
+
YOUTUBE_REQUEST_NONE
No request/invalid.
+
YOUTUBE_REQUEST_USER_FAVORITES
The item was created as a result of a favorites
request.
+
YOUTUBE_REQUEST_VIDEO_SEARCH
The item was created as a result of a search request.
+
YOUTUBE_REQUEST_USER_UPLOADS
The item was created as a result of a request for
videos that were uploaded by a particular user.
+
YOUTUBE_REQUEST_STANDARD_FEED
The item was created as a result of a request for one
of the standard feeds.
+
YOUTUBE_REQUEST_USER_PLAYLISTS
The item was created as a result of a request for
users playlists.
+
YOUTUBE_REQUEST_USER_SUBSCRIPTIONS
The item was created as a result of for users
subscriptions.
The following aux keys can be used to retrieve additional
information about the item, the data is stored in the form of
strings. Note, that depending on the requests some tags may not
be set and will return empty values.
*
orig.aux[YOUTUBE_AUXDATA_KEYWORDS]
RO
Contains a space separated list of keywords for the
particular item.
*
orig.aux[YOUTUBE_AUXDATA_AVG_RATING]
RO
Contains the average rating value.
*
orig.aux[YOUTUBE_AUXDATA_AUTHOR]
RO
Contains the author name.
*
orig.aux[YOUTUBE_AUXDATA_FEED]
RO
Contains the name of the feed.
*
orig.aux[YOUTUBE_AUXDATA_VIEW_COUNT]
RO
Contains the view count of the video on the YouTube
website.
*
orig.aux[YOUTUBE_AUXDATA_REGION]
RO
Contains the name of the region.
*
orig.aux[YOUTUBE_AUXDATA_RATING_COUNT]
RO
Contains the rating count of the video on the YouTube
website.
*
orig.aux[YOUTUBE_AUXDATA_REQUEST]
RO
Contains the name of the request that produced this item
(i.e. Favorites, Popular, etc.), this is the human readable
representation of the orig.yt_request property.
*
orig.aux[YOUTUBE_AUXDATA_SUBREQUEST_NAME]
RO
Contains the additional name that accompanies the request,
it is only set for the playlist and subcription requests.
*
orig.aux[YOUTUBE_AUXDATA_CATEGORY]
RO
Contains the name of the category of the item.
3.1.3. Weborama Properties
When the obj.onlineservice variable equals
ONLINE_SERVICE_WEBORAMA the item has the following additional
aux property:
orig.aux[WEBORAMA_AUXDATA_REQUEST_NAME]
This property holds the name of the request that generated this
object, it is the name that you specify in the config.xml file,
i.e. in the below example the value of
orig.aux[WEBORAMA_AUXDATA_REQUEST_NAME] will be 'My Playlist':
<playlist name="My Playlist" type="playlist" mood="dark"/>
3.1.4. Apple Trailers Properties
When the obj.onlineservice variable equals
ONLINE_SERVICE_APPLE_TRAILERS the item has the following
additional aux property:
orig.aux[APPLE_TRAILERS_AUXDATA_POST_DATE]
This property holds the date when the trailer was posted, the
date format is YYYY-MM-DD.
Note:
the orig.meta[M_DATE] property holds the release date of
the movie.
3.1.5. DVD Properties
Version 0.12.0 introduces an additional import script for DVD
images. The DVD image is parsed with the help of libdvdread,
the information about the available titles, chapters,
languages, etc. is gathered and provided to the DVD import
script. The usual object properties apply here as well, however
the dvd object offers several extensions that can be accessed
via the aux property:
dvd.aux[DVD]
*
dvd.aux[DVD].titles
RO
This is an array that contains information about titles
that are found on the DVD. The length of the array (and
thus the number of available titles can be retrieved by:
dvd.aux[DVD].titles.length
Further, being a normal JavaScript array it supports all
associated JS functions.
*
dvd.aux[DVD].titles[t_index].audio_tracks
RO
Each title object in the titles array provides information
about available audio tracks, the audio_tracks is an array
as well. The t_index variable is only used as an example in
this case and represents an integer index value in the
range:
dvd.aux[DVD].titles.length > t_index >= 0
*
dvd.aux[DVD].titles[t_index].audio_tracks[a_index].format
RO
A string, containing the format name of the audio track
(i.e. ac3, dts, etc.). The a_index variable is only used as
an example, it represents an integer index value in the
range:
dvd.aux[DVD].titles[t_index].audio_tracks.length > a_index >= 0
*
dvd.aux[DVD].titles[t_index].audio_tracks[a_index].language
RO
A string, containing the name of the language of the audio
track.
*
dvd.aux[DVD].titles[t_index].chapters
RO
This property is an array which contains chapter
information for the particular title.
*
dvd.aux[DVD].titles[t_index].chapters[c_index].duration
RO
Duration from the start of the chapter to the end of the
movie. Chapter at index 0 will always have the duration of
the whole title. The c_index variable is only used as an
example, it represents an integer index value in the range:
dvd.aux[DVD].titles[t_index].chapters.length > c_index >= 0
3.2. Constants
Actually there are no such things as constants in JS, so those
are actually predefined global variables that are set during JS
engine initialization. Do not assign any values to them,
otherwise following script invocation will be using wrong
values.
*
UPNP_CLASS_CONTAINER
Type: string
Value: object.container
*
UPNP_CLASS_CONTAINER_MUSIC_ARTIST
Type: string
Value: object.container.person.musicArtist
*
UPNP_CLASS_CONTAINER_MUSIC_GENRE
Type: string
Value: object.container.genre.musicGenre
*
UPNP_CLASS_CONTAINER_MUSIC_ALBUM
Type: string
Value: object.container.album.musicAlbum
Note:
this container class will be treated by the server
in a special way, all music items in this
container will be sorted by ID3 track number.
*
UPNP_CLASS_PLAYLIST_CONTAINER
Type: string
Value: object.container.playlistContainer
Note:
this container class will be treated by the server
in a special way, all items in this container will
be sorted by the number specified in the
playlistOrder property (this is set when an object
is created by the playlist script).
*
UPNP_CLASS_ITEM
Type: string
Value: object.item
*
UPNP_CLASS_ITEM_MUSIC_TRACK
Type: string
Value: object.item.audioItem.musicTrack
*
UPNP_CLASS_ITEM_VIDEO
Type: string
Value: object.item.videoItem
*
UPNP_CLASS_ITEM_IMAGE
Type: string
Value: object.item.imageItem
*
OBJECT_TYPE_CONTAINER
Type: integer
Value: 1
*
OBJECT_TYPE_ITEM
Type: integer
Value: 2
*
OBJECT_TYPE_ACTIVE_ITEM
Type: integer
Value: 4
*
OBJECT_TYPE_ITEM_EXTERNAL_URL
Type: integer
Value: 8
*
OBJECT_TYPE_ITEM_INTERNAL_URL
Type: integer
Value: 16
4. Functions
The server offers various native functions that can be called
from the scripts, additionally there are some js helper
functions that can be used.
4.1. Native Server Functions
The so called native functions are implemented in C++ in the
server and can be called from the scripts.
4.1.1. Native Functions Available To All Scripts
The server offers three functions which can be called from
within the import and/or the playlist script:
*
addCdsObject(object, containerChain, lastContainerClass);
This function adds a virtual object to the server database,
the path in the database is defined by the containerChain
parameter. The third argument is optional, it allows to set
the upnp:class of the last container in the chain.
Parameters:
+
object
A virtual object that is either a copy of or a
reference to 'orig', see Section 3.2 for a list of
properties.
+
containerChain
A string, defining where the object will be added in
the database hierarchy. The containers in the chain
are separated by a slash '/', for example, a value of
'/Audio/All Music' will add the object to the Audio,
All Music container in the server hierarchy. Make sure
to properly escape the slash characters in container
names. You will find more information on container
chain escaping later in this chapter.
+
lastContainerClass
A string, defining the upnp:class of the container
that appears last in the chain. This parameter can be
omitted, in this case the default value
'object.container' will be taken. Setting specific
upnp container classes is useful to define the special
meaning of a particular container; for example, the
server will always sort songs by track number if upnp
class of a container is set to
'object.container.album.musicAlbum'.
*
copyObject(originalObject);
This function returns a copy of the virtual object.
*
print(...);
This function is useful for debugging scripts, it simply
prints to the standard output.
*
f2i(string)
*
m2i(string)
*
p2i(string)
*
j2i(string)
The above set of functions converts predefined characters
sets to UTF-8. The 'from' charsets can be defined in the
server configuration:
+ f2i: filesystem charset to internal
+ m2i: metadata charset to internal
+ j2i: js charset to internal
+ p2i: playlist charset to internal
4.1.2. Native Functions Available To The Playlist Script
The following function is only available to the playlist
script.
*
readln();
This function reads and returns exactly one line of text
from the playlist that is currently being processed, end of
line is identified by carriage return/line feed characters.
Each subsequent call will return the next line, there is no
way to go back.
The idea is, that you can process your playlist line by
line and gather the required information to create new
objects which can be added to the database.
4.1.3. Native Functions Available To The DVD Import Script
The following function is only available to the DVD import
script.
*
addCdsObject(object, containerChain, lastContainerClass);
This function adds a virtual object to the server database,
the path in the database is defined by the containerChain
parameter. The third argument is optional, it allows to set
the upnp:class of the last container in the chain.
Parameters:
+
object
A virtual object that is either a copy of or a
reference to 'orig', see Section 3.2 for a list of
properties.
+
containerChain
A string, defining where the object will be added in
the database hierarchy. The containers in the chain
are separated by a slash '/', for example, a value of
'/Audio/All Music' will add the object to the Audio,
All Music container in the server hierarchy. Make sure
to properly escape the slash characters in container
names. You will find more information on container
chain escaping later in this chapter.
+
lastContainerClass
A string, defining the upnp:class of the container
that appears last in the chain. This parameter can be
omitted, in this case the default value
'object.container' will be taken. Setting specific
upnp container classes is useful to define the special
meaning of a particular container; for example, the
server will always sort songs by track number if upnp
class of a container is set to
'object.container.album.musicAlbum'.
*
addDVDObject(dvd, t, c, a, createContainerChain(chain));
This function reads and returns exactly one line of text
from the playlist that is currently being processed, end of
line is identified by carriage return/line feed characters.
Each subsequent call will return the next line, there is no
way to go back.
The idea is, that you can process your playlist line by
line and gather the required information to create new
objects which can be added to the database.
4.2. Helper Functions
There is a set of helper JavaScript functions which reside in
the common.js script. They can be used by the import and by the
playlist script.
*
function escapeSlash(name);
The first function escapes slash '/' characters in a
string. This is necessary, because the container chain is
defined by a slash separated string, where slash has a
special meaning - it defines the container hierarchy. That
means, that slashes that appear in the object's title need
to be properly escaped.
*
The following function makes it easier to work with
container chains; it takes an array of container names as
argument, makes sure that the names are properly escaped
and adds the slash separators as necessary. It returns a
string that is formatted to be used as a parameter for the
addCdsObject function.
function createContainerChain(arr)
{
var path = '';
for (var i = 0; i < arr.length; i++)
{
path = path + '/' + escapeSlash(arr[i]);
}
return path;
}
* This function retrieves the year from a yyyy-mm-dd
formatted string.
function getYear(date)
{
var matches = date.match(/^([0-9]{4})-/);
if (matches)
return matches[1];
else
return date;
}
* This function identifies the type of the playlist by the
mimetype, it is used in the playlist script to select an
appropriate parser.
function getPlaylistType(mimetype)
{
if (mimetype == 'audio/x-mpegurl')
return 'm3u';
if (mimetype == 'audio/x-scpls')
return 'pls';
return '';
}
5. Walkthrough
Now it is time to take a closer look at the default scripts
that are supplied with MediaTomb. Usually it is installed in
the /usr/share/mediatomb/js/ directory, but you will also find
it in scripts/js/ in the MediaTomb source tree.
Note:
this is not a JavaScript tutorial, if you are new to JS
you should probably make yourself familiar with the
language.
5.1. Import Script
We start with a walkthrough of the default import script, it is
called import.js in the MediaTomb distribution.
Below are the import script functions that organize our content
in the database by creating the virtual structure. Each media
type - audio, image and video is handled by a separate
function.
5.1.1. Audio Content Handler
The biggest one is the function that handles audio - the reason
is simple: mp3 files offer a lot of metadata like album,
artist, genre, etc. information, this allows us to create a
nice container layout.
function addAudio(obj)
{
var desc = '';
var artist_full;
var album_full;
First we will gather all the metadata that is provided by our
object, of course it is possible that some fields are empty -
we will have to check that to make sure that we handle this
case correctly.
var title = obj.meta[M_TITLE];
Note the difference between obj.title and obj.meta[M_TITLE] -
while object.title will originally be set to the file name,
obj.meta[M_TITLE] will contain the parsed title - in this
particular example the ID3 title of an MP3.
if (!title) title = obj.title;
var artist = obj.meta[M_ARTIST];
if (!artist)
{
artist = 'Unknown';
artist_full = null;
}
else
{
artist_full = artist;
desc = artist;
}
var album = obj.meta[M_ALBUM];
if (!album)
{
album = 'Unknown';
album_full = null;
}
else
{
desc = desc + ', ' + album;
album_full = album;
}
if (desc)
desc = desc + ', ';
desc = desc + title;
var date = obj.meta[M_DATE];
if (!date)
{
date = 'Unknown';
}
else
{
date = normalizeDate(date);
desc = desc + ', ' + date;
}
var genre = obj.meta[M_GENRE];
if (!genre)
{
genre = 'Unknown';
}
else
{
desc = desc + ', ' + genre;
}
var description = obj.meta[M_DESCRIPTION];
if (!description)
{
Note how we are setting properties of an object - in this case
we put together a description and we are setting for objects
that did not already have one.
obj.meta[M_DESCRIPTION] = desc;
}
We finally gathered all data that we need, so let's create a
nice layout for our audio files. Note how we are constructing
the chain, in the line below the array 'chain' will be
converted to 'Audio/All audio' by the createContainerChain()
function.
var chain = new Array('Audio', 'All audio');
obj.title = title;
The UPnP class argument to addCdsObject() is optional, if it is
not supplied the default UPnP class will be used. However, it
is suggested to correctly set UPnP classes of containers and
objects - this information may be used by some renderers to
identify the type of the container and present the content in a
different manner .
addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER
_MUSIC);
chain = new Array('Audio', 'Artists', artist, 'All songs');
addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER
_MUSIC);
chain = new Array('Audio', 'All - full name');
var temp = '';
if (artist_full)
temp = artist_full;
if (album_full)
temp = temp + ' - ' + album_full + ' - ';
obj.title = temp + title;
addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER
_MUSIC);
chain = new Array('Audio', 'Artists', artist, 'All - full name');
addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER
_MUSIC);
chain = new Array('Audio', 'Artists', artist, album);
obj.title = track + title;
Remember, the server will sort all items by ID3 track if the
container class is set to UPNP_CLASS_CONTAINER_MUSIC_ALBUM.
addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER
_MUSIC_ALBUM);
chain = new Array('Audio', 'Albums', album);
obj.title = track + title;
addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER
_MUSIC_ALBUM);
chain = new Array('Audio', 'Genres', genre);
addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER
_MUSIC_GENRE);
chain = new Array('Audio', 'Year', date);
addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER
_MUSIC);
}
5.1.2. Weborama Content Handler
Weborama content handler is really simple, the service aims at
providing 'radio on demand', so everything here maps to a
search query that you specified in the config.xml:
function addWeborama(obj)
{
var req_name = obj.aux[WEBORAMA_AUXDATA_REQUEST_NAME];
if (req_name)
{
var chain = new Array('Online Services', 'Weborama', req_name);
addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_PLAYL
IST_CONTAINER);
}
}
5.1.3. Image Content Handler
This function takes care of images. Currently it does very
little sorting, but could easily be extended - photos made by
digital cameras provide lots of information in the Exif tag, so
you could easily add code to sort your pictures by camera model
or anything Exif field you might be interested in.
Note:
if you want to use those additional Exif fields you need
to compile MediaTomb with libexif support and also
specify the fields of interest in the import section of
your configuration file (See documentation about
library-options).
function addImage(obj)
{
var chain = new Array('Photos', 'All Photos');
addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER
);
var date = obj.meta[M_DATE];
if (date)
{
chain = new Array('Photos', 'Date', date);
addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTA
INER);
}
}
Just like in the addAudio() function - we simply construct our
container chain and add the object.
5.1.4. Video Content Handler
Not much to say here... I think libextractor is capable of
retrieving some information from video files, however I seldom
encountered any video files populated with metadata. You could
also try ffmpeg to get more information, however by default we
keep it very simple - we just put everything into the 'All
Video' container.
function addVideo(obj)
{
var chain = new Array('Video');
addCdsObject(obj, createContainerChain(chain));
}
5.1.5. YouTube Content Handler
This helper function processes items that are imported from the
YouTube service; these items have extended properties that were
described in detail earlier in this document. Let's have a look
at how they are used:
function addYouTube(obj)
{
var chain;
First, we want to sort the content by average rating. Remember
- all properties in the obj.aux array are strings, so we will
do an extra conversion because we want to round the rating.
var temp = parseInt(obj.aux[YOUTUBE_AUXDATA_AVG_RATING], 10);
Make sure to check that we got a number.
if (temp != Number.NaN)
{
temp = Math.round(temp);
Here is the place if you want to have a different range of
ratings in your tree structure:
if (temp > 3)
{
var chain = new Array('Online Services', 'YouTube',
'Rating', temp.toString());
addCdsObject(obj, createContainerChain(chain));
}
}
Next, we process the request, i.e. - description of the request
to the YouTube service that created the item. Some requests may
contain additional information like the name of the region.
temp = obj.aux[YOUTUBE_AUXDATA_REQUEST];
if (temp)
{
var subName = (obj.aux[YOUTUBE_AUXDATA_SUBREQUEST_NAME]);
var feedName = (obj.aux[YOUTUBE_AUXDATA_FEED]);
var region = (obj.aux[YOUTUBE_AUXDATA_REGION]);
chain = new Array('Online Services', 'YouTube', temp);
All items will go to /Online Services/YouTube/RequestName/,
below we will do additional refinement. Do not forget to check
if the values are valid, some requests may not have all of the
tags set (for example - if no specific region was defined in
the config, then the associated request will not provide the
REGION auxdata)
if (subName)
chain.push(subName);
if (feedName)
chain.push(feedName);
if (region)
chain.push(region);
addCdsObject(obj, createContainerChain(chain));
}
}
5.1.6. Apple Trailers Content Handler
This function processes items that are importent via the Apple
Trailers feature. We will organize the trailers by genre, post
date and release date, additionally we will also add a
container holding all trailers.
function addTrailer(obj)
{
var chain;
First we will add the item to the 'All Trailers' container, so
that we get a nice long playlist:
chain = new Array('Online Services', 'Apple Trailers', 'All Trailer
s');
addCdsObject(obj, createContainerChain(chain));
We also want to sort the trailers by genre, however we need to
take some extra care here: the genre property here is a comma
separated value list, so one trailer can have several matching
genres that will be returned as one string. We will split that
string and create individual genre containers.
var genre = obj.meta[M_GENRE];
if (genre)
{
A genre string "Science Fiction, Thriller" will be split to
"Science Fiction" and "Thriller" respectively.
genres = genre.split(', ');
for (var i = 0; i < genres.length; i++)
{
chain = new Array('Online Services', 'Apple Trailers', 'Gen
res',
genres[i]);
addCdsObject(obj, createContainerChain(chain));
}
}
The release date is offered in a YYYY-MM-DD format, we won't do
too much extra checking regading validity, however we only want
to group the trailers by year and month:
var reldate = obj.meta[M_DATE];
if ((reldate) && (reldate.length >= 7))
{
chain = new Array('Online Services', 'Apple Trailers', 'Release
Date',
reldate.slice(0, 7));
addCdsObject(obj, createContainerChain(chain));
}
We also want to group the trailers by the date when they were
originally posted, the post date is available via the aux
array. Similar to the release date, we will cut off the day and
create our containres in the YYYY-MM format.
var postdate = obj.aux[APPLE_TRAILERS_AUXDATA_POST_DATE];
if ((postdate) && (postdate.length >= 7))
{
chain = new Array('Online Services', 'Apple Trailers', 'Post Da
te',
postdate.slice(0, 7));
addCdsObject(obj, createContainerChain(chain));
}
}
5.1.7. Putting it all together
This is the main part of the script, it looks at the mimetype
of the original object and feeds the object to the appropriate
content handler.
if (getPlaylistType(orig.mimetype) == '')
{
var arr = orig.mimetype.split('/');
var mime = arr[0];
var obj = orig;
All virtual objects are references to objects in the
PC-Directory, so make sure to correctly set the reference ID!
obj.refID = orig.id;
if ((mime == 'audio'))
{
We support the Weborama online radio service, so we will do
some extra handling for those items:
if (obj.onlineservice == ONLINE_SERVICE_WEBORAMA)
addWeborama(obj);
else
addAudio(obj);
}
if (mime == 'video')
{
We support the YouTube service which offers video items, so we
will do an extra check to sort it properly:
if (obj.onlineservice == ONLINE_SERVICE_YOUTUBE)
addYouTube(obj);
else
addVideo(obj);
}
if (mime == 'image')
{
addImage(obj);
}
We now also have OGG Theora recognition, so we can ensure that
Vorbis
if (orig.mimetype == 'application/ogg')
{
if (obj.theora == 1)
addVideo(obj);
else
addAudio(obj);
}
}
5.2. Playlist Script
The default playlist parsing script is called playlists.js,
similar to the import script it works with a global object
which is called 'playlist', the fields are similar to the
'orig' that is used in the import script with the exception of
the playlistOrder field which is special to playlists.
Another big difference between playlist and import scripts is,
that playlist scripts can add new media to the database, while
import scripts only process already existing objects (the ones
found in PC Directory) and just add additional virtual items.
The default playlist script implementation supports parsing of
m3u and pls formats, but you can add support for parsing of any
ASCII based playlist format.
5.2.1. Adding Items
We will first look at a helper function:
addPlaylistItem(location, title, playlistChain);
It is defined in playlists.js, it receives the location (path
on disk or HTTP URL), the title and the desired position of the
item in the database layout (remember the container chains used
in the import script).
The function first decides if we are dealing with an item that
represents a resource on the web, or if we are dealing with a
local file. After that it populates all item fields accordingly
and calls the addCdsObject() that was introduced earlier. Note,
that if the object that is being added by the playlist script
is not yet in the database, the import script will be invoked.
Below is the complete function with some comments:
function addPlaylistItem(location, title, playlistChain)
{
Determine if the item that we got is an URL or a local file.
if (location.match(/^.*:\/\//))
{
var exturl = new Object();
Setting the mimetype is crucial and tricky... if you get it
wrong your renderer may show the item as unsupported and refuse
to play it. Unfortunately most playlist formats do not provide
any mimetype information.
exturl.mimetype = 'audio/mpeg';
Make sure to correctly set the object type, then populate the
remaining fields.
exturl.objectType = OBJECT_TYPE_ITEM_EXTERNAL_URL;
exturl.location = location;
exturl.title = (title ? title : location);
exturl.protocol = 'http-get';
exturl.upnpclass = UPNP_CLASS_ITEM_MUSIC_TRACK;
exturl.description = "Song from " + playlist.title;
This is a special field which ensures that your playlist files
will be displayed in the correct order inside a playlist
container. It is similar to the id3 track number that is used
to sort the media in album containers.
exturl.playlistOrder = playlistOrder++;
Your item will be added to the container named by the playlist
that you are currently parsing.
addCdsObject(exturl, playlistChain, UPNP_CLASS_PLAYLIST_CONTAI
NER);
}
Here we are dealing with a local file.
else
{
if (location.substr(0,1) != '/')
location = playlistLocation + location;
var item = new Object();
item.location = location;
if (title)
item.title = title;
else
{
var locationParts = location.split('/');
item.title = locationParts[locationParts.length - 1];
if (! item.title)
item.title = location;
}
item.objectType = OBJECT_TYPE_ITEM;
item.playlistOrder = playlistOrder++;
addCdsObject(item, playlistChain, UPNP_CLASS_PLAYLIST_CONTAINE
R);
}
}
5.2.2. Main Parsing
The actual parsing is done in the main part of the script.
First, the type of the playlist is determined (based on the
playlist mimetype), then the correct parser is chosen. The
parsing itself is a loop, where each call to readln() returns
exactly one line of text from the playlist. There is no
possibility to go back, each readln() invocation will retrieve
the next line until end of file is reached.
To keep things easy we will only list the m3u parsing here.
Again, if you are not familiar with regular expressions, now is
probably the time to take a closer look.
...
else if (type == 'm3u')
{
var line;
var title = null;
Here is the do - while loop which will read the playlist line
by line.
do
{
Read the line:
line = readln();
Perform m3u specific parsing:
if (line.match(/^#EXTINF:(\d+),(\S.+)$/i))
{
// duration = RegExp.$1; // currently unused
title = RegExp.$2;
}
else if (! line.match(/^(#|\s*$)/))
{
Call the helper function to add the item once you gathered the
data:
addPlaylistItem(line, title, playlistChain);
title = null;
}
}
We will exit the loop when end of the playlist file is reached.
while (line);
}
...
5.3. DVD Import Script
The DVD import script receives an object that represents a DVD
image. The object provides information about the number of
titles, chapters, audio tracks and about languages that are
available in the image. You can not play the ISO directly (most
players will not support this), so we weill create special
virtual DVD objects, which will deliver an MPEG PES stream for
the selected Title/Audio Track/Chapter.
The DVD import script is separated from the main script, the
script that is shipped with the default installation is called
import-dvd.js.
Let's have a closer look!
The title of the DVD will be set to the file name of the ISO
image, we want to get rid of the .iso extension:
var title = dvd.title;
var index = title.lastIndexOf('.');
if (index > 1)
title = title.substring(0, index);
Since the object that we receive is the original ISO it will
not have the correct video UPnP class, so we have to set it
ourselves:
dvd.upnpclass = UPNP_CLASS_ITEM_VIDEO;
Now we will get the number of titles and loop through them,
creating a virtual structure for the chapters, languages and
audio formats:
var title_count = dvd.aux[DVD].titles.length;
for (var t = 0; t < title_count; t++)
{
var title_name = 'Title';
Since the sorting is based on the titles we need a leading
zero. Also note the (t + 1) part, the very first position in
the array has an index of zero, however we want that the title
count starts with one in the UI:
if (t < 9)
title_name = title_name + ' 0' + (t + 1);
else
title_name = title_name + ' ' + (t + 1);
Get the number of chapters and audio tracks for this title and
loop through them:
var chapter_count = dvd.aux[DVD].titles[t].chapters.length;
var audio_track_count = dvd.aux[DVD].titles[t].audio_tracks.length;
for (var a = 0; a < audio_track_count; a++)
{
var chain;
Again, note the (a + 1) part, we want the first track in the UI
to show as Track 01 and not Track 00:
var audio_name = ' - Audio Track ' + (a + 1);
We will create a structure, sorting the media by audio
languages and formats:
var audio_language = dvd.aux[DVD].titles[t].audio_tracks[a].lan
guage;
var audio_format = dvd.aux[DVD].titles[t].audio_tracks[a].forma
t;
if (audio_format != '')
{
if (audio_language != '')
audio_name = audio_name + ' - ' + audio_language;
chain = new Array('Video', 'DVD', title, 'Audio Formats',
audio_format, title_name + audio_name);
The code above was only dealing with containers, this loop will
create the actual playable items:
for (var c = 0; c < chapter_count; c++)
{
if (c < 9)
dvd.title = "Chapter 0" + (c + 1);
else
dvd.title = "Chapter " + (c + 1);
When attempted to play, the item created below will deliver the
MPEG PES with title index t, chapter index c and audio track
index a - we created the chain appropriately so that the audio
index matches the language and audio format that we used in the
container names:
addDVDObject(dvd, t, c, a, createContainerChain(chain))
;
}
}
Same for the language:
if (audio_language != '')
{
chain = new Array('Video', 'DVD', title, 'Languages',
audio_language);
if (audio_format != '')
chain.push(title_name + audio_name + ' - ' + audio_form
at);
else
chain.push(title_name + audio_name);
for (var c = 0; c < chapter_count; c++)
{
if (c < 9)
dvd.title = "Chapter 0" + (c + 1);
else
dvd.title = "Chapter " + (c + 1);
addDVDObject(dvd, t, c, a, createContainerChain(chain))
;
}
}
And we also want a list of titles with appropriate format and
language information:
chain = new Array('Video', 'DVD', title, 'Titles');
var titles = title_name + ' - Audio Track ' + (a + 1);
if (audio_format != '')
titles = titles + ' - ' + audio_format;
if (audio_language != '')
titles = titles + ' - ' + audio_language;
chain.push(titles);
for (var c = 0; c < chapter_count; c++)
{
if (c < 9)
dvd.title = "Chapter 0" + (c + 1);
else
dvd.title = "Chapter " + (c + 1);
addDVDObject(dvd, t, c, a, createContainerChain(chain));
}
}
}
Happy scripting!
|