2023-04-08 02:33:38 -04:00
using Newtonsoft.Json ;
using System.Linq ;
using System.Diagnostics ;
using System.Text.RegularExpressions ;
using ttrss_co_client.ttrss ;
using ttrss_co_client.ttrss.datastructures ;
namespace ttrss_co_client.tasks
{
///<summary>Move to output</summary>
public class Sponsorblock : Phase2Task
{
public override string TaskName = > "sponsorblock" ;
public override async Task < Tuple < TaskStatus , WorkOrder > > ActOn ( WorkOrder workOrder )
{
var sw = new Stopwatch ( ) ;
sw . Start ( ) ;
var article = ( await TtrssClient . GetArticles ( workOrder . articleId ) ) . FirstOrDefault ( ) ;
if ( article = = null )
{
Console . Error . WriteLine ( $"couldn't find article {workOrder.articleId} for workOrder {workOrder.guid}?!" ) ;
return new Tuple < TaskStatus , WorkOrder > ( TaskStatus . Done , null ) ;
}
if ( ! article . link . Host . EndsWith ( "youtube.com" ) )
{
Console . WriteLine ( "no sponsorblock segments on sites other than YT" ) ;
return new Tuple < TaskStatus , WorkOrder > ( TaskStatus . ContinueNow , workOrder ) ;
}
var match = Regex . Match ( article . link . Query , "v=([^&]+)(&|$)" ) ;
var videoId = match . Groups ? [ 1 ] . Value ;
var c = new HttpClient ( ) ;
var sponsorblockcheck = await c . GetAsync ( $"https://sponsor.ajay.app/api/skipSegments?videoID={videoId}&category=sponsor&category=selfpromo&category=interaciton&category=intro&category=outro&category=preview" ) ;
IEnumerable < sponsorblock . Segment > segments = null ;
try
{
segments = JsonConvert . DeserializeObject < IEnumerable < sponsorblock . Segment > > ( await sponsorblockcheck . Content . ReadAsStringAsync ( ) ) ;
}
catch ( Exception e )
{
segments = null ;
}
if ( sponsorblockcheck . StatusCode = = System . Net . HttpStatusCode . NotFound | | segments = = null | | segments . Count ( ) = = 0 )
{
Console . WriteLine ( $"sponsorblock reports that {videoId} has no entries (yet)" ) ;
var updateTimestamp = new DateTime ( 1970 , 1 , 1 , 0 , 0 , 0 , 0 , DateTimeKind . Utc ) ;
updateTimestamp = updateTimestamp . AddSeconds ( article . updated ) . ToLocalTime ( ) ;
if ( DateTime . Now - updateTimestamp > TimeSpan . FromMinutes ( 45 ) )
{
await TtrssClient . UpdateArticleNote ( $"{article.note}\n[{DateTime.Now.ToLongTimeString()}] updated {updateTimestamp} (more than 45 minutes ago), going to give up waiting for sponsorblock" , article . id ) ;
return new Tuple < TaskStatus , WorkOrder > ( TaskStatus . ContinueNow , workOrder ) ;
}
else
{
await TtrssClient . UpdateArticleNote ( $"{article.note}\n[{DateTime.Now.ToLongTimeString()}] waiting for sponsorblock segments" , article . id ) ;
workOrder . Phase2TaskList [ workOrder . Phase2TaskList . Keys . Min ( ) - 1 ] = this . TaskName ;
return new Tuple < TaskStatus , WorkOrder > ( TaskStatus . TryLater , workOrder ) ;
}
}
else
{
var contentSegments = new List < Tuple < double , double > > ( ) ;
var previousEdge = 0.0 ;
var extension = workOrder . data [ "path" ] . Substring ( workOrder . data [ "path" ] . LastIndexOf ( '.' ) ) ;
var intermediatePathBase = workOrder . data [ "path" ] . Substring ( 0 , workOrder . data [ "path" ] . LastIndexOf ( '.' ) ) ;
var conversionProcesses = new List < Tuple < Process , Tuple < int , string > > > ( ) ;
foreach ( var junkSegment in segments . OrderBy ( s = > s . segment [ 0 ] ) )
{
contentSegments . Add ( new Tuple < double , double > ( previousEdge , junkSegment . segment [ 0 ] ) ) ;
previousEdge = junkSegment . segment [ 1 ] ;
if ( previousEdge < 0.5 )
{
contentSegments . Clear ( ) ;
}
}
contentSegments . Add ( new Tuple < double , double > ( previousEdge , segments . First ( ) . videoDuration ) ) ;
var intermediateCount = 0 ;
foreach ( var seg in contentSegments )
{
var intermediateTargetPath = $"{intermediatePathBase}-intermediate-{intermediateCount.ToString(" ddd ")}{extension}" ;
conversionProcesses . Add ( new Tuple < Process , Tuple < int , string > > (
Process . Start ( "ffmpeg" , $"-y -i \" { workOrder . data [ "path" ] } \ " \"{intermediateTargetPath}\"" ) ,
new Tuple < int , string > ( intermediateCount , intermediateTargetPath )
) ) ;
intermediateCount + + ;
}
Console . WriteLine ( $"[{DateTime.Now.ToLongTimeString()}] intermediate content segments being exported" ) ;
var intermediates = new Dictionary < int , string > ( ) ;
foreach ( var proc in conversionProcesses )
{
proc . Item1 . WaitForExit ( ) ;
intermediates [ proc . Item2 . Item1 ] = proc . Item2 . Item2 ;
}
Console . WriteLine ( $"[{DateTime.Now.ToLongTimeString()}] intermediate content segments should be exported, stitching together" ) ;
Process . Start ( "ffmpeg" , $"-y -i \" concat : { string . Join ( '|' , intermediates . OrderBy ( kvp = > kvp . Key ) . Select ( kvp = > kvp . Value ) . ToArray ( ) ) } \ " -c copy \"{workOrder.data[" path "]}\"" )
. WaitForExit ( ) ;
Console . WriteLine ( $"[{DateTime.Now.ToLongTimeString()}] intermediate content segments stitched. Deleting originals." ) ;
foreach ( var intermediate in intermediates . Values )
{
File . Delete ( intermediate ) ;
}
sw . Stop ( ) ;
await TtrssClient . UpdateArticleNote ( $"{article.note}\n[{DateTime.Now.ToLongTimeString()}] removed {segments.Count()} junk segments" , article . id ) ;
return new Tuple < TaskStatus , WorkOrder > ( TaskStatus . ContinueNow , workOrder ) ;
}
}
}
}