mlbserver 2025.8.20 → 2025.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.js +83 -0
  2. package/package.json +1 -1
  3. package/session.js +54 -4
package/index.js CHANGED
@@ -2528,6 +2528,8 @@ app.get('/', async function(req, res) {
2528
2528
  let example_streamURL = gamechanger_streamURL + '&' + gamechanger_types[i] + 'cludeTeams=' + include_teams
2529
2529
  body += '&bull; ' + gamechanger_types[i] + 'clude: <a href="' + http_root + '/embed.html?src=' + encodeURIComponent(example_streamURL) + '&startFrom=' + VALID_START_FROM[1] + content_protect_b + '">Embed</a> | <a href="' + example_streamURL + '">Stream</a> | <a href="' + http_root + '/chromecast.html?src=' + encodeURIComponent(example_streamURL) + content_protect_b + '">Chromecast</a> | <a href="' + http_root + '/advanced.html?src=' + encodeURIComponent(example_streamURL) + content_protect_b + '">Advanced</a> | <a href="' + http_root + '/kodi.strm?src=' + encodeURIComponent(example_streamURL) + content_protect_b + '">Kodi</a><br/>' + "\n"
2530
2530
  }
2531
+
2532
+ body += '<p><span class="tooltip">Comskip link examples<span class="tooltiptext">You can generate a <a href="https://github.com/erikkaashoek/Comskip">Comskip</a>-style file to automatically skip sections (breaks, idle time, or non-action pitches) of games you record using DVR software when watched in compatible players. For example, if you record a game from your local OTA channel using Tvheadend, you can then fetch one of these Comskip files, put it in the same directory with the same name as your recorded video file, and Kodi will automatically skip those sections while you watch the video.<br><br>Specifying the team and broadcast_start_timestamp in the URL is required! For the timestamp, use the time your DVR software began the recording. This should be your local time in YYYY-MM-DDTHH:MM:SS format.<br><br>Specifying a skip_adjust value in the URL is recommended, to adjust for broadcast delays. This will vary across different channels and different video sources.<br><br>For the txt file format, specifying the video frame rate (fps) in the URL is also required. This will commonly be either 30, 59.94, or 60, depending on your video source.<br><br>Optionally, setting pad to "on" will generate random extra skips at the end, to help avoid timeline spoilers.</span></span>: <a href="' + http_root + '/comskip.edl?team=CHC&date=2025-10-01&pad=on&skip=pitches&skip_adjust=11&broadcast_start_timestamp=2025-10-01T14:00:00' + content_protect_a + '">comskip.edl</a> or <a href="' + http_root + '/comskip.txt?team=CHC&date=2025-10-01&pad=on&skip=pitches&skip_adjust=11&broadcast_start_timestamp=2025-10-01T14:00:00&fps=59.94' + content_protect_a + '">comskip.txt</a></p>' + "\n"
2531
2533
 
2532
2534
  body += '</p></td></tr></table><br/>' + "\n"
2533
2535
 
@@ -3650,3 +3652,84 @@ app.get('/stream_finder_icon.png', async function(req, res) {
3650
3652
  res.end('stream_finder_icon.png request error, check log')
3651
3653
  }
3652
3654
  })
3655
+
3656
+ function prepareReqForComskip(req) {
3657
+ req.query.mediaType = req.query.mediaType || VALID_MEDIA_TYPES[0]
3658
+ req.query.inning_half = req.query.inning_half || VALID_INNING_HALF[0]
3659
+ req.query.inning_number = req.query.inning_number || VALID_INNING_NUMBER[0]
3660
+ let skip = req.query.skip || VALID_SKIP[1]
3661
+ req.query.skip = VALID_SKIP.indexOf(skip)
3662
+ req.query.pad = req.query.pad || VALID_PAD[0]
3663
+ req.query.skip_adjust = parseInt(req.query.skip_adjust) || DEFAULT_SKIP_ADJUST
3664
+ return req
3665
+ }
3666
+
3667
+ // Listen for Comskip EDL file requests
3668
+ app.get('/comskip.edl', async function(req, res) {
3669
+ if ( ! (await protect(req, res)) ) return
3670
+
3671
+ try {
3672
+ session.requestlog('comskip.edl', req)
3673
+
3674
+ req = prepareReqForComskip(req)
3675
+
3676
+ let comskip_markers = await session.getComskipMarkers(req)
3677
+
3678
+ var body = ''
3679
+
3680
+ for (var i=0; i<comskip_markers.length; i++) {
3681
+ body += comskip_markers[i].break_start.toFixed(3) + ' ' + comskip_markers[i].break_end.toFixed(3) + ' 3' + "\n"
3682
+ }
3683
+
3684
+ var download_headers = {
3685
+ 'Content-Disposition': 'attachment; filename="comskip.edl"'
3686
+ }
3687
+ res.writeHead(200, download_headers)
3688
+
3689
+ res.end(body)
3690
+ } catch (e) {
3691
+ session.log('comskip.edl request error : ' + e.message)
3692
+ res.end('comskip.edl request error, check log')
3693
+ }
3694
+ })
3695
+
3696
+ // Listen for Comskip TXT file requests
3697
+ app.get('/comskip.txt', async function(req, res) {
3698
+ if ( ! (await protect(req, res)) ) return
3699
+
3700
+ try {
3701
+ session.requestlog('comskip.txt', req)
3702
+
3703
+ var body = ''
3704
+
3705
+ if ( !req.query.fps ) {
3706
+ session.log('comskip.txt error : specifying the video FPS in the request URL is required')
3707
+ } else {
3708
+ let fps = req.query.fps
3709
+
3710
+ req = prepareReqForComskip(req)
3711
+
3712
+ let comskip_markers = await session.getComskipMarkers(req)
3713
+
3714
+ if ( comskip_markers.length > 0 ) {
3715
+ let last_marker = comskip_markers[comskip_markers.length-1].break_end
3716
+
3717
+ body = 'FILE PROCESSING COMPLETE ' + Math.round(last_marker * fps) + ' FRAMES AT ' + (fps * 100) + "\n" + '-------------------' + "\n"
3718
+
3719
+ for (var i=0; i<comskip_markers.length; i++) {
3720
+ body += Math.round(comskip_markers[i].break_start * fps) + "\t" + Math.round(comskip_markers[i].break_end * fps) + "\n"
3721
+ }
3722
+ }
3723
+ }
3724
+
3725
+ var download_headers = {
3726
+ 'Content-Disposition': 'attachment; filename="comskip.txt"'
3727
+ }
3728
+ res.writeHead(200, download_headers)
3729
+
3730
+ res.end(body)
3731
+ } catch (e) {
3732
+ session.log('comskip.txt request error : ' + e.message)
3733
+ res.end('comskip.txt request error, check log')
3734
+ }
3735
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlbserver",
3
- "version": "2025.08.20",
3
+ "version": "2025.10.02",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
package/session.js CHANGED
@@ -3252,7 +3252,7 @@ class sessionClass {
3252
3252
  }
3253
3253
 
3254
3254
  // Get skip markers into temporary cache
3255
- async getSkipMarkers(gamePk, skip_type, start_inning, start_inning_half, streamURL, streamURLToken, skip_adjust) {
3255
+ async getSkipMarkers(gamePk, skip_type, start_inning, start_inning_half, streamURL, streamURLToken, skip_adjust, broadcast_start_timestamp=false) {
3256
3256
  try {
3257
3257
  this.debuglog('getSkipMarkers')
3258
3258
 
@@ -3276,9 +3276,11 @@ class sessionClass {
3276
3276
  // assume the game starts in a break
3277
3277
  let break_start = 0
3278
3278
 
3279
- // Get the broadcast start time first -- event times will be relative to this
3280
- let variantPlaylist = await this.getVariantPlaylist(streamURL, streamURLToken)
3281
- let broadcast_start_timestamp = await this.getBroadcastStart(variantPlaylist)
3279
+ // Get the broadcast start time first, if necessary -- event times will be relative to this
3280
+ if ( !broadcast_start_timestamp ) {
3281
+ let variantPlaylist = await this.getVariantPlaylist(streamURL, streamURLToken)
3282
+ broadcast_start_timestamp = await this.getBroadcastStart(variantPlaylist)
3283
+ }
3282
3284
 
3283
3285
  if ( broadcast_start_timestamp ) {
3284
3286
  this.debuglog('getSkipMarkers broadcast start detected as ' + broadcast_start_timestamp)
@@ -5409,6 +5411,54 @@ class sessionClass {
5409
5411
  }
5410
5412
  return title
5411
5413
  }
5414
+
5415
+ async getComskipMarkers(req) {
5416
+ try {
5417
+ let gamePk = req.query.gamePk
5418
+
5419
+ if ( !gamePk && req.query.team ) {
5420
+ let mediaType = req.query.mediaType
5421
+ let level = req.query.level || 'MLB'
5422
+ let includeBlackouts = 'true'
5423
+ let mediaInfo = await this.getMediaId(decodeURIComponent(req.query.team), decodeURIComponent(level), mediaType, req.query.date, req.query.game, includeBlackouts)
5424
+ gamePk = mediaInfo.gamePk
5425
+ }
5426
+
5427
+ if ( !req.query.broadcast_start_timestamp ) {
5428
+ this.log('comskip error : specifying the broadcast_start_timestamp in the URL is required, should be your local time in YYYY-MM-DDTHH:MM:SS format')
5429
+ return []
5430
+ }
5431
+ let broadcast_start_timestamp = new Date(req.query.broadcast_start_timestamp)
5432
+
5433
+ let inning_half = req.query.inning_half
5434
+ let inning_number = req.query.inning_number
5435
+ let skip_type = req.query.skip
5436
+ let pad = req.query.pad
5437
+
5438
+ let streamURL = 'false'
5439
+ let streamURLToken = 'false'
5440
+
5441
+ let skip_adjust = parseInt(req.query.skip_adjust)
5442
+
5443
+ await this.getSkipMarkers(gamePk, skip_type, inning_number, inning_half, streamURL, streamURLToken, skip_adjust, broadcast_start_timestamp)
5444
+
5445
+ let comskip_markers = this.temp_cache[gamePk].skip_markers
5446
+
5447
+ if ( (pad == 'on') && (comskip_markers.length > 0) ) {
5448
+ let last_marker = comskip_markers[comskip_markers.length-1].break_end
5449
+ let pad_time = last_marker + Math.floor(Math.random() * 7200) + 3600
5450
+ while ( last_marker < pad_time ) {
5451
+ let pad_start = last_marker + ((Math.floor(Math.random() * 15000) + 75000) / 1000)
5452
+ last_marker = pad_start + ((Math.floor(Math.random() * 15000) + 75000) / 1000)
5453
+ comskip_markers.push({'break_start': pad_start, 'break_end': last_marker})
5454
+ }
5455
+ }
5456
+
5457
+ return comskip_markers
5458
+ } catch(e) {
5459
+ this.log('getComskipMarkers error : ' + e.message)
5460
+ }
5461
+ }
5412
5462
  }
5413
5463
 
5414
5464
  module.exports = sessionClass