2023-04-09 17:01:25 -04:00
using System ;
using System.Text ;
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
{
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 ( ) ;
2023-04-09 17:01:25 -04:00
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&category=interaction" ) ;
2023-04-08 02:33:38 -04:00
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 ) )
{
2023-04-09 17:01:25 -04:00
await TtrssClient . UpdateArticleNote ( $"{article.note}\n[{DateTime.Now.ToString(" o ")}] updated {updateTimestamp} (more than 45 minutes ago), going to give up waiting for sponsorblock" , article . id ) ;
2023-04-08 02:33:38 -04:00
return new Tuple < TaskStatus , WorkOrder > ( TaskStatus . ContinueNow , workOrder ) ;
}
else
{
2023-04-09 17:01:25 -04:00
await TtrssClient . UpdateArticleNote ( $"{article.note}\n[{DateTime.Now.ToString(" o ")}] waiting for sponsorblock segments" , article . id ) ;
2023-04-08 02:33:38 -04:00
workOrder . Phase2TaskList [ workOrder . Phase2TaskList . Keys . Min ( ) - 1 ] = this . TaskName ;
return new Tuple < TaskStatus , WorkOrder > ( TaskStatus . TryLater , workOrder ) ;
}
}
else
{
2023-04-09 17:01:25 -04:00
Console . WriteLine ( $"[{DateTime.Now.ToString(" o ")}] sponsorblock reports {segments.Count()} junk segments" ) ;
2023-04-08 02:33:38 -04:00
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 > > > ( ) ;
2023-07-06 22:31:53 -04:00
2023-04-08 02:33:38 -04:00
foreach ( var junkSegment in segments . OrderBy ( s = > s . segment [ 0 ] ) )
{
contentSegments . Add ( new Tuple < double , double > ( previousEdge , junkSegment . segment [ 0 ] ) ) ;
previousEdge = junkSegment . segment [ 1 ] ;
}
contentSegments . Add ( new Tuple < double , double > ( previousEdge , segments . First ( ) . videoDuration ) ) ;
2023-04-09 17:22:15 -04:00
contentSegments = contentSegments . Except ( contentSegments . Where ( tup = > tup . Item2 - tup . Item1 < 0.5 ) ) . ToList ( ) ;
2023-04-08 02:33:38 -04:00
2023-04-09 17:01:25 -04:00
#region ffmpeg via intermediate files
2023-04-08 02:33:38 -04:00
var intermediateCount = 0 ;
foreach ( var seg in contentSegments )
{
2023-04-09 17:01:25 -04:00
var intermediateTargetPath = $"{intermediatePathBase}-intermediate-{intermediateCount.ToString(" D2 ")}{extension}" ;
conversionProcesses . Add ( new Tuple < Process , Tuple < int , string > > (
Process . Start ( "ffmpeg" , $"-y -loglevel quiet -i \" { workOrder . data [ "path" ] } \ " -ss {seg.Item1} -to {seg.Item2} \"{intermediateTargetPath}\"" ) ,
new Tuple < int , string > ( intermediateCount , intermediateTargetPath )
) ) ;
Console . WriteLine ( "waiting for exit from task, for debugging reasons" ) ;
conversionProcesses . Last ( ) . Item1 . WaitForExit ( ) ;
2023-04-08 02:33:38 -04:00
intermediateCount + + ;
}
2023-04-09 17:01:25 -04:00
Console . WriteLine ( $"[{DateTime.Now.ToString(" o ")}] intermediate content segments being exported" ) ;
2023-04-08 02:33:38 -04:00
var intermediates = new Dictionary < int , string > ( ) ;
foreach ( var proc in conversionProcesses )
{
proc . Item1 . WaitForExit ( ) ;
intermediates [ proc . Item2 . Item1 ] = proc . Item2 . Item2 ;
}
2023-04-09 17:01:25 -04:00
Console . WriteLine ( $"[{DateTime.Now.ToString(" o ")}] intermediate content segments should be exported, stitching together" ) ;
var sb = new StringBuilder ( ) ;
sb . AppendLine ( "#ffmpeg demands it" ) ;
foreach ( var intermediate in intermediates . OrderBy ( kvp = > kvp . Key ) )
{
sb . AppendLine ( $"file '{intermediate.Value}'" ) ;
}
var ffmpegFile = Path . Combine ( Path . GetDirectoryName ( workOrder . data [ "path" ] ) , "ffmpeglist.txt" ) ;
File . WriteAllText ( ffmpegFile , sb . ToString ( ) ) ;
Process . Start ( "ffmpeg" , $"-y -f concat -safe 0 -i {ffmpegFile} -c copy \" { workOrder . data [ "path" ] } \ "" )
2023-04-08 02:33:38 -04:00
. WaitForExit ( ) ;
2023-04-09 17:01:25 -04:00
File . Delete ( ffmpegFile ) ;
2023-04-08 02:33:38 -04:00
2023-04-09 17:01:25 -04:00
Console . WriteLine ( $"[{DateTime.Now.ToString(" o ")}] intermediate content segments stitched. Deleting originals." ) ;
2023-04-08 02:33:38 -04:00
foreach ( var intermediate in intermediates . Values )
2023-07-06 22:31:53 -04:00
{
2023-04-08 02:33:38 -04:00
File . Delete ( intermediate ) ;
}
2023-04-09 17:01:25 -04:00
#endregion
2023-04-08 02:33:38 -04:00
sw . Stop ( ) ;
2023-04-09 17:01:25 -04:00
await TtrssClient . UpdateArticleNote ( $"{article.note}\n[{DateTime.Now.ToString(" o ")}] removed {segments.Count()} junk segments" , article . id ) ;
2023-04-08 02:33:38 -04:00
return new Tuple < TaskStatus , WorkOrder > ( TaskStatus . ContinueNow , workOrder ) ;
}
}
}
}