Skip to content

Commit

Permalink
* Added an inital tivomindrpc client for the websocket. tivo does not…
Browse files Browse the repository at this point in the history
… send the intermderary cert and node does not download needed ones so I've included the comodo cert that's needed.

* Added Get recording skip.  This will create a metadata file you can import with ffmpeg.
  • Loading branch information
lart2150 committed Aug 22, 2021
1 parent 164f839 commit d6ce994
Show file tree
Hide file tree
Showing 10 changed files with 591 additions and 10 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/config.js
/cdata.*
/*.ts
/node_modules
/node_modules
/*.txt
10 changes: 10 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ This script will look how long you have guide data. if it's less then 10 days i

This is more an example/poc script that gets all of the recordings on a tive except for deleted recordings. You can use it to get a recording id to download.

### getRecordingSkip.js
`node getRecordingSkip.js tivo:rc.12345678`

this will get the recording metadata, skip metadata, and skip data offset. It will write the data to a file like "title SxE subtitle.txt"

you can then add the data to a recording you have downloaded using ffmpeg.

`ffmpeg -i "Title - 1x01 - sub title.ts" -i "Title - 1x01 - sub title.txt" -map_metadata 1 -c copy "Title - 1x01 - sub title.mkv"`


### getStatus.js
`node getStatus.js`

Expand Down
6 changes: 5 additions & 1 deletion config.js.dist
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@ export const config = {
deleteRegex: /^(CBS|NBC|ABC|FOX) .* News/,
deleteScanAll: false,//false it will only check the 50 most recently deleted recordings. false is way faster if you have a lot of deleted recordings
deleteDuplicats: false, //setting this to true will flip deleteScanAll to true.
tivoDecodePath: "C:\\tivodecode\\tivodecode.exe"//I strongly recommend tivodecode-ng
tivoDecodePath: "C:\\tivodecode\\tivodecode.exe",//I strongly recommend tivodecode-ng

//used for tivo online
domainToken: 'asdfasdfasdf',//you need to pull this cookie from online.tivo.com. at some point I'm going to write this to take a username/password and get the cookie. but for now this is here.
tsn: 'tsn:000000000000000',//after I get loggin working I'll pull the first tsn from http://online.tivo.com/body/global/selectors
};
132 changes: 132 additions & 0 deletions getRecordingSkip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
'use strict'
import {TivoWs} from './lib/tivo-ws.js';
import {config} from './config.js';
import {formatName} from './lib/util.js';
import fs from 'fs';

const doit = async (recordingId) => {
const tivo = new TivoWs(
config.domainToken,
config.tsn
);
await tivo.connect();
console.log('connected');

//const recordingId = "tivo:rc.16052569";

const recordingInfo = await tivo.getRecordingInfo(recordingId);
const recording = recordingInfo.recording[0];

let skip = null;
if (recordingInfo.recording && recordingInfo.recording[0]) {
if (recording.clipMetadata && recording.clipMetadata[0] && recording.clipMetadata[0].segmentType === 'adSkip') {
skip = await tivo.cipMetaDataSearch(recording.clipMetadata[0].clipMetadataId, recordingId);
//console.log(JSON.stringify(skip));
}
} else {
console.error('Recording does not have Skip metadata');
return;
}

skip = {"authorId":"kinetiq_program_segments_1.2","channelAffiliate":"NBC Affiliate","channelNumber":"1203","channelSourceType":"cable","channelStationId":"tivo:st.69023168","clipMetadataId":"tivo:cm.260698","contentEpisodeNumber":7,"contentId":"tivo:ct.445501732","contentSeasonNumber":3,"contentSubtitle":"Take It Our Back","contentTitle":"Making It","createDate":"1970-01-01","description":"version 1.0","offerStartTime":"2021-08-20 01:00:00","segment":[{"endOffset":"4088185","keyword":["AUTO_SKIP_MODE"],"startOffset":"3613358","type":"clipSegment"},{"endOffset":"4893182","startOffset":"4297965","type":"clipSegment"},{"endOffset":"5421111","startOffset":"5096965","type":"clipSegment"},{"endOffset":"5991095","startOffset":"5616241","type":"clipSegment"},{"endOffset":"6586110","startOffset":"6205927","type":"clipSegment"},{"endOffset":"7232220","startOffset":"6780910","type":"clipSegment"}],"segmentType":"adSkip","type":"clipMetadata"};

if (!skip.segment?.length > 0) {
console.error('failed to pull skip metadata');
return;
}



const streaming = await tivo.sendRequest({
"clientUuid": "5678",
"deviceConfiguration": {
"deviceType": "webPlayer",
"type": "deviceConfiguration"
},
"sessionType": "streaming",
"hlsStreamDesiredVariantsSet": "ABR",
"supportedEncryption": {
"type": "hlsStreamEncryptionInfo",
"encryptionType": "hlsAes128Cbc"
},
"isLocal": true,
"recordingId": recordingId,
"type": "hlsStreamRecordingRequest",
responseCount: "multiple",
}, true);

//console.log('streaming', streaming);
//console.log('playlistUrl', `http://${config.ip}:49152${streaming.hlsSession.playlistUri}`);

const hlsSessionId = streaming.hlsSession.hlsSessionId;

let offset = -1;
try {
//this seems to work most of the time
let whatsOn = await tivo.sendRequest({"hlsSessionId":hlsSessionId,"type":"whatsOnSearch", SchemaVersion: 38});
//console.log('whatsOn', whatsOn.whatsOn[0]);

if (whatsOn.whatsOn[0].recordingId === recordingId) {
//console.log('SUCESS! offset: ' + whatsOn.whatsOn[0].streamPositionMs)
offset = whatsOn.whatsOn[0].streamPositionMs;
}
} finally {
const cancle = await tivo.sendRequest({"clientUuid":"5678","hlsSessionId":hlsSessionId,"type":"hlsStreamRelease"});
//console.log('cancle', cancle);
}
tivo.disconnect();

if(offset < 0) {
console.log('failed to find skip offset');
return;
}

const segments = skip.segment.sort((a, b) => a.startOffset - b.startOffset);

let metadata = `;FFMETADATA1
AirDate=${recording.actualStartTime}
Date=${recording.originalAirdate}
RecordingTimestamp=${recording.actualStartTime}
show=${recording.collectionTitle}
season_number=${recording.seasonNumber}
comment=${recording.description}
title=${recording.collectionTitle}
`;
let id = 1;
let skipId = 1;
let lastEnd = 0;
for (const chaper of segments) {
const start = chaper.startOffset - offset;
const end = chaper.endOffset - offset;

if (start - lastEnd > 10000) {//if it's more then a 10 second gap lets make an ad chapter
metadata += `[CHAPTER]
TIMEBASE=1/1000
START=${lastEnd}
END=${start}
title=Skip ${skipId++}
`;
}
lastEnd = end;

metadata += `[CHAPTER]
TIMEBASE=1/1000
START=${start}
END=${end}
title=Content ${id++}
`
}

const outName = formatName(recording) + '.txt';
//console.log(metadata);
fs.writeFileSync(outName, metadata);
}

if (process.argv.length < 3) {
console.error ('this requires a recording ID');
} else {
doit(process.argv[2]);
}
Loading

0 comments on commit d6ce994

Please sign in to comment.