
    z1jK                       d Z ddlmZ ddlZej
                  j                  dd       ddlmZ	  e	d      Z
ddlmZ ddlZddlZddlZddlZddlZddlZddlZddlZddlmZmZ dd	lmZ  ed
      Zd!dZd"dZd#dZd$dZd%dZd&dZd'dZ d(dZ!d)dZ"d*dZ#d+dZ$d,dZ%d-dZ&d.dZ'd/dZ(d0dZ)d1dZ*d2d3dZ+d4dZ,e-dk(  r7e
j]                  d        ej^                   e,ej`                  d d              yy)5u_  
Aria Knowledge Pipeline (AKP) — Stage 1: Ingest
KAR-74 · 2026-05-12

Liest Channel-Liste aus brain/05-Referenzen/youtube-channel-subscriptions.md,
holt neue Videos via yt-dlp --flat-playlist (kein YouTube-Data-API-Key noetig),
filtert per Cheap-Filter (title-blocklist + duration), pullt Transcripts via
youtube_transcript_api (timedtext-Endpoint, ~KB/Video — KAR-742), mit yt-dlp
Auto-Subs nur noch als gecappten Fallback, schreibt JSON nach
brain/youtube/raw/<channel>/<video-id>.json, trackt State in brain/youtube/.state.sqlite.

Discovery-Bypass: liest *.url / *.txt aus brain/youtube/discover/.
    )annotationsNz/root/aria/lib)
get_loggerzaria-akp-ingest)audit)datetimetimezone)Pathz%/root/aria/brain/youtube/.config.yamlc                     t         j                         5 } t        j                  |       cd d d        S # 1 sw Y   y xY wN)CONFIG_PATHopenyaml	safe_load)fs    %/root/aria/scripts/aria-akp-ingest.pyload_configr      s1    				 !q~~a ! ! !s   4=c                Z    t        j                  |       }t         j                  |_        |S r
   )sqlite3connectRowrow_factory)db_pathcons     r   db_connr   "   s     
//'
"CkkCOJ    c                   g }t        j                  d      }t        |       5 }|D ]h  }|j                  |j	                               }|s%|j                  d      }|j                  d      j                         }|j                  ||d       j 	 ddd       t               }g }	|D ]/  }
|
d   |v r|j                  |
d          |	j                  |
       1 |	S # 1 sw Y   KxY w)zJParse channel list from markdown memory file. Returns [{handle/id, slug}].z"^-\s+(@\w[\w.-]*|UC[\w-]{20,24})\b   @)identslugNr   )recompiler   matchstripgrouplstriplowerappendsetadd)memory_pathoutline_rer   linemr   r   seendedupedcs              r   parse_channelsr2   (   s    Cjj>?G	k	 7a 	7Ddjjl+AGGAJE<<$**,DJJ56	77 5DG W:7q	
 N7 7s   A.CC%c                b    | j                  d      rd|  dS | j                  d      rd|  dS | S )Nr   zhttps://www.youtube.com/z/videosUCz https://www.youtube.com/channel/)
startswith)r   s    r   channel_urlr6   >   sA    )%881%@@Lr   c                   ddddt        |      ddddg	}|r t        |      j                         r|d	|gz  }|r|d
|gz  }|j                  |        	 t	        j
                  |ddd      }|j                  dk7  r|j                  j                         sg S 	 t        j                  |j                        }t        |t              sg S |j                  d      xs g }g }|D ]  }	|	r|	j                  d      dvr|	j                  d      }
|
s-|j                  |
|	j                  d      xs d|	j                  d      |	j                  d      xs d|
 |	j                  d      xs |	j                  d      xs d|	j                  d      xs dd        |S # t        j                  $ r g cY S w xY w# t        j                  $ r g cY S w xY w)u?   Fetch flat-playlist of channel — only metadata, no downloads.yt-dlpz--flat-playlistz-Jz--playlist-endz--ignore-errors--no-warnings--socket-timeout30	--cookies--proxyTx   capture_outputtexttimeoutr   entries_type)Nurlidtitle durationrE   z https://www.youtube.com/watch?v=uploaderchannelupload_date)video_idrG   rI   rE   rJ   rL   )strr   existsr'   
subprocessrunTimeoutExpired
returncodestdoutr#   jsonloadsJSONDecodeError
isinstancedictget)rE   	max_itemscookiesproxycmdrdatarC   r+   evids              r   yt_dlp_flatrc   F   s    	#i.DC 4='')W%%	5!!JJsONN3t$L 	||q!1	zz!((# dD!	hhy!'RG
C AEE'N-7eeDk

UU7^)rj)55<K%EcU#Kj)CQUU9-=C55/52
 	 J5 $$ 	  	s$   F F6 F32F36GGc                   ddl }|j                  d      }g d}|r|d|gz  }|r t        |      j                         r|d|gz  }	 dg|d	d
| }	 t	        j
                  |ddd      }|j                  j                         rt        j                  |j                        nd}dg|d	dddddd| d| }		 t	        j
                  |	ddd      }
t!        t        |      j#                  d            }|s|
j$                  xs dj                         j'                         dd xs dg}t)        d|  d|d   dd  t*        j$                         d|ft        |      j                         D ]  }	 |j                           	 t        |      j                          S t-        |d   j/                  d            }||ft        |      j                         D ]  }	 |j                           	 t        |      j                          S # t        j                  t        j                  f$ r d}Y w xY w# t        j                  $ rn d|fcY t        |      j                         D ]"  }	 |j                          # t        $ r Y  w xY w 	 t        |      j                          S # t        $ r Y S w xY ww xY w# t        $ r Y Xw xY w# t        $ r Y S w xY w# t        $ r Y w xY w# t        $ r Y S w xY w# t        |      j                         D ]"  }	 |j                          # t        $ r Y  w xY w 	 t        |      j                          w # t        $ r Y w w xY wxY w)a  Pull auto-subs + metadata. Returns (transcript_text, metadata_dict).

    Two-call pattern: first metadata via --dump-json (lightweight), then sub-only
    download. Combining --write-sub with -J is unreliable because yt-dlp tries
    to extract video formats too.
    r   Nzakp-)prefix)r9   r:   60r=   r<   r8   --skip-download--dump-jsonT<   r?   z--write-auto-subz
--sub-langz
en.*,en,dez--sub-formatvttz-oz/%(id)s.%(ext)sr>   z*.vttrH   z  [no_transcript] : filereplaceerrors)tempfilemkdtempr   rO   rP   rQ   rT   r#   rU   rV   rR   rW   iterdirunlinkOSErrorrmdirsortedglobstderr
splitlinesprintsysvtt_to_text	read_text)rE   r\   r]   rr   tmp	base_argsmeta_cmdmrmetasub_cmdsrp	vtt_fileserrrA   s                  r   yt_dlp_transcriptr   u   s    


&

)C;Ii''	4='')k7++	$PyP*;P]PCP	DRTUB,.IIOO,=4::bii(4D 
 

 
 	
 '	

 

 "
 
 E)
 
	4QTUB 49>>'23	99?))+668=E"C&se2c!fTcl^<3::N: c""$ 	A
	
	IOO 9Q<111CDTzc""$ 	A
	
	IOO; ))4+?+?@ 	D	 (( 	: c""$ 	A
 	
	IOO 		#	    			    		 c""$ 	A
 	
	IOO 		s  L AH 'L H? BL ;KK(!L &K"9K2%H<8L ;H<<L ?K L 4J	JJJ00	J<;J<?K  L 	KK	KK"	K/.K/2	K>=K>M*L0/M*0	L<	9M*;L<	<M*MM*	M'$M*&M''M*c                   g }d}| j                         D ]  }|j                         }|r|j                  d      r'd|v st        j                  d|      rBt        j
                  dd|      }t        j
                  dd|      j                         }|s||k7  s|j                  |       |} dj                  |      S )	z=Strip VTT timestamps + cue numbers, dedupe consecutive lines.rH   )WEBVTTNOTEzKind:z	Language:z-->z^\d+$z<[^>]+>z&\w+; 
)r{   r#   r5   r    r"   subr'   join)rj   	out_lineslastrawr-   cleaneds         r   r~   r~      s    ID~~ 
yy{t'OPD=BHHXt4&&R.&&30668w$W%D
 99Yr   c                :   	 ddl m}m}m}m}m}m}m}	m}
m	} ddl
m} |r
 |||      nd} ||	      }	 |j!                  | t#        |      
      }dj3                  d |D              }|r|dfS dS # t        $ r(}t        d| t        j                         Y d}~yd}~ww xY w# ||f$ r>}t        d|  dt%        |      j&                   t        j                         Y d}~yd}~w|$ r 	 t)        t+        |j-                  |             d      }|Y y|j!                         }nT# ||f$ r Y Y yt.        $ r?}t        d|  dt%        |      j&                   t        j                         Y d}~Y yd}~ww xY wY -|||	|
|f$ r>}t        d|  dt%        |      j&                   t        j                         Y d}~yd}~wt.        $ rM}t        d|  dt%        |      j&                   dt1        |      dd  t        j                         Y d}~yd}~ww xY w)a]  KAR-742: primary transcript path via youtube_transcript_api.

    Trifft nur den `timedtext`-Endpoint (~KB statt MB/Video via yt-dlp Watch-Page).
    Proxy als GenericProxyConfig (unser Webshare-Setup nutzt IP-Auth ohne User/Pass,
    deshalb NICHT WebshareProxyConfig).

    Returns (text_or_None, status) mit status in:
      "ok"      transcript geholt
      "none"    Video hat nachweislich kein Transcript -> skip, kein yt-dlp-Fallback
      "blocked" Proxy quota/IP-Block -> Lauf abbrechen (Bandbreite schonen)
      "error"   transienter Fehler -> optionaler, gecappter yt-dlp-Fallback erlaubt
    r   )	YouTubeTranscriptApiRequestBlocked	IpBlockedTranscriptsDisabledNoTranscriptFoundVideoUnavailableVideoUnplayableAgeRestrictedInvalidVideoId)GenericProxyConfigz  [ytapi_import_fail] rm   N)Nerror)http_url	https_url)proxy_config)	languagesz  [ytapi_blocked] rl   )Nblocked)Nnonez  [ytapi_none] z  [ytapi_error] r>   r   c              3     K   | ]:  }t        |d d      j                         s|j                  j                          < yw)rA   rH   N)getattrr#   rA   ).0ss     r   	<genexpr>z#ytapi_transcript.<locals>.<genexpr>   s.     W628N8T8T8VQVV\\^Ws
    AAok)youtube_transcript_apir   r   r   r   r   r   r   r   r   youtube_transcript_api.proxiesr   ImportErrorr|   r}   rz   fetchtupletype__name__nextiterlist	ExceptionrN   r   )rM   r]   r   r   r   r   r   r   r   r   r   r   r   ra   pcapifetchedfirstrA   s                      r   ytapi_transcriptr      s   		
 	
 	

 	F
 AF	Ue	<4B
B
/C))Hi0@)A. 99WWWWDD$<3^3?  &qc*< I& "8*BtAw/?/?.@A

S  		 chhx0148E}#kkmG	* 	#" 	 OH:RQ0@0@/ABT	    !1?MSab zDG,<,<+=>SZZP  
"T!W-=-=,>bQOVYV`V`as   A2 B& 2	B#;BB#&H-4C&&H/&D(D('H(E9/H2E9:4E4.H4E99H?	H4GHAHHc                ~   g d}|r|d|gz  }|r t        |      j                         r|d|gz  }|j                  |        	 t        j                  |ddd      }|j
                  j                         rt        j                  |j
                        S dS # t        j                  t        j                  f$ r Y yw xY w)u   Metadata-only fetch (--dump-json) fuer Discovery-Items (selten, manuell).

    Channel-Videos brauchen das NICHT — deren Metadaten kommen aus --flat-playlist.
    )r8   r9   r:   rf   rg   rh   r=   r<   Tri   r?   N)r   rO   r'   rP   rQ   rT   r#   rU   rV   rR   rW   )rE   r\   r]   r^   r_   s        r   yt_dlp_metar      s    
 bC	5!!4='')W%%JJsONN3t$K'(xx~~'7tzz!((#ATA%%t';';< s   AB B #B<;B<c                    | j                  d      xs d}| j                  d      }|d   d   }|r&t        j                  ||t        j                        ry|||d   d   k  ry	||d   d
   kD  ryy)z#Return skip-reason or None if pass.rG   rH   rI   ingesttitle_blocklist_regextitle_blocklistNduration_min_seconds	too_shortduration_max_secondstoo_long)rZ   r    search
IGNORECASE)videocfgrG   rI   	blocklists        r   cheap_filterr     s~    IIg$"Eyy$HH56IRYYy%? c(m$:;;c(m$:;;r   c                L    | j                  d|f      }|j                         d uS )Nz'SELECT 1 FROM ingested WHERE video_id=?)executefetchone)r   rM   curs      r   already_ingestedr     s&    
++?(
MC<<>%%r   c                    | j                  d|d   ||j                  dd      |j                  d      |j                  dd      |f       | j                          y )NzuINSERT OR IGNORE INTO ingested (video_id, channel, title, duration_seconds, url, skipped_reason) VALUES (?,?,?,?,?,?)rM   rG   rH   rI   rE   r   rZ   commit)r   r   channel_slugreasons       r   store_skippedr     sV    KK	z	L%))GR*@%))JBWY^YbYbchjlYmouv JJLr   c                    | j                  d|d   ||j                  d|      |j                  dd      |j                  d      |j                  dd      |j                  dd      |||j                  d	d
      f
       | j                          y )NzINSERT OR REPLACE INTO ingested (video_id, channel, channel_handle, title, duration_seconds, published_at, url, transcript_chars, raw_path, source) VALUES (?,?,?,?,?,?,?,?,?,?)rM   rJ   rG   rH   rI   rL   rE   _sourcerK   r   )r   r   r   raw_pathtranscript_charss        r   store_ingestedr      s    KK 	{*IIj,/IIgr"IIj!IImR(IIeR IIi+	
 JJLr   c                   t        |d   d         |z  }|j                  dd       || d    dz  }| d   || j                  d      xs || j                  dd	      | j                  d
      | j                  dd	      | j                  dd	      |t        |      t	        j
                  t        j                        j                         | j                  dd      |r|j                  d      nd |r|j                  d      nd |r|j                  d      nd |r|j                  d      nd |r|j                  d      xs g d d nd dd}|j                  t        j                  |dd             t        |      S )Npathsraw_dirT)parentsexist_okrM   z.jsonrJ   rG   rH   rI   rE   rL   r   rK   
view_count
like_count
channel_id
categoriestags   )r   r   r   r   r   )rM   r   channel_handlerG   duration_secondsrE   rL   
transcriptr   ingested_atsourceyt_meta   F)indentensure_ascii)r   mkdirrZ   lenr   nowr   utc	isoformat
write_textrU   dumpsrN   )r   r   r   r   r   out_dirout_pathpayloads           r   	write_rawr   3  sS   3w<	*+l:GMM$M.E*-.e44H*%$))J/?<7B'!IIj1yy#yy3 
O||HLL1;;=))Iy148$((<0d48$((<0d48$((<0d48$((<0d59TXXf%+Sb1t
G( 

715IJx=r   c           	     &   g }t        | d   d         }|j                         s|S t        j                  d      }|j	                  d      D ]W  }|j                  d      }|j                  |      D ]/  }|j                  |j                  d      dt        |      d	       1 Y |j	                  d
      D ]W  }|j                  d      }|j                  |      D ]/  }|j                  |j                  d      dt        |      d	       1 Y |S )zBRead discovery folder for *.url and *.txt files with YouTube URLs.r   discover_dirz?https?://[^\s'\"<>]+(?:youtube\.com/watch\?v=|youtu\.be/)[\w-]+z*.urlro   rp   r   discover)rE   r   
_drop_filez*.txt)
r   rO   r    r!   ry   r   finditerr'   r$   rN   )r   r+   r   url_rer   rA   r.   s          r   collect_discoveryr  O  s   CG^45L 
ZZZ[Fw' Y{{){,& 	YAJJqwwqzjPSTUPVWX	YY w' Y{{){,& 	YAJJqwwqzjPSTUPVWX	YY Jr   c                X    t        j                  d|       }|r|j                  d      S d S )Nz(?:v=|youtu\.be/)([\w-]{6,})r   )r    r   r$   )rE   r.   s     r   video_id_from_urlr  a  s(    
		137A1771:$$r   c                \    | j                  d      }| j                          |j                  S )Nz@INSERT INTO run_log (stage, status) VALUES ('ingest', 'running'))r   r   	lastrowid)r   r   s     r   log_run_startr  f  s$    
++X
YCJJL==r   c                R    | j                  d|||||f       | j                          y )NzUPDATE run_log SET finished_at=datetime('now'), status='completed', items_processed=?, items_skipped=?, items_failed=?, notes=? WHERE id=?)r   r   )r   run_id	processedskippedfailednotess         r   log_run_finishr  l  s-    KK 	U	GVUF3 JJLr   c                	   t               }|d   d   }t        |      }t        |d   d         }t        |      }|d   j	                  d      }t
        j                  j	                  d      xs |d   j	                  d      xs d }|d   j	                  d      xs g d	}t        |d   j	                  d
d            }	d}
d}|d   d   }d| v r|d   d   }d }d| v rG| j                  d      }|dz   t        |       k  r%| |dz      j                         j                  d      }|r|D cg c]  }|d   |k(  s| }}t        |      }d}d}d}g }|D ]1  }|r n,t        |d         }t        ||||      }|s|j                  |d    d       ?|D ]  }|r Ht!        ||d         rt#        ||      }|rt%        |||d   |       |dz  }<t'        |d   ||      \  }}d }|dk(  rd}|j                  d        |s"|dk(  r|
|	k  r|
dz  }
t)        |d   ||      \  }}|st%        |||d   d       |dz  }t+        ||d   |||      }t-        |||d   |t        |             |dz  }t/        j0                  d        4 |D ]  }|r nt3        |d         }|rt!        ||      r	 t5        |d          j7                          C||d<   d!|d"<   t'        |||      \  }}|dk(  rd}|j                  d#        nH|s"|dk(  r|
|	k  r|
dz  }
t)        |d   ||      \  }} |st%        ||d!d |d   d$d!d       |dz  }t=        |d   ||      }|xs i j	                  d%d!      j                         j?                  d&d'      }!||xs i j	                  d(d)      |xs i j	                  d*      |d   |xs i j	                  d+      xs |xs i j	                  d%      xs d!|xs i j	                  d,d)      d!d-}t+        ||!|||      }t-        |||!|t        |             |dz  }	 t5        |d          j7                           tA        |||||d.jC                  |      d d/        tE        d0| d1| d2| d3|
 d4| 
       |r|d d D ]  }"tE        d5|"         |jG                          yc c}w # t8        t:        f$ r Y w xY w# t8        $ r Y Nw xY w)6Nr   state_dbchannel_memoryr   yt_dlp_cookiesYT_PROXYyt_dlp_proxytranscript_languages)endezen-USzen-GByt_dlp_fallback_max   r   Fvideos_per_channel_per_runz
--backfillbackfillvideos_per_channel_initialz--onlyr   r   r   r   z: 0 videos returnedrM   r   Tu5   ytapi proxy blocked — Lauf abgebrochen (Bandbreite)r   rE   no_transcriptg      ?r   r   r   u/   ytapi proxy blocked (discovery) — abgebrochen)rM   rG   rI   rE   rK   r   -rG   rH   rI   rJ   rL   )rM   rG   rI   rE   rJ   rL   r   z; i  z[akp-ingest] processed=z	 skipped=z failed=z yt_dlp_fallback=z	 blocked=z  NOTE: )$r   r   r2   r  rZ   osenvironintindexr   r&   r%   r  r6   rc   r'   r   r   r   r   r   r   r   timesleepr  r   ru   rv   KeyErrorr   ro   r  r   r|   close)#argvr   r   r   channelsdiscoveriesr\   r]   r   fallback_maxfallback_usedr   max_per_channelonly_channelidxr1   r
  r  r  r  notes_lineschrE   videosvr   r   statusr   r   drb   _ch_slugns#                                      r   mainr9  t  s   
-C'l:&G
'
Cc'l+;<=H#C(K(m 01GJJNN:&S#h-*;*;N*KStE H!!"89[=[Is8}(()>BCLMG(m$@AOtj/*FGL4jj"7SYa=..077<L'E!1V9+DAEE3FIGFK $"W+&S/7EB"W+.ABC 	AQz]3!!S)Fc1bj&91 "2!J-	!RJD"""#Z[&G"38T"#4QuXw#N 
Dc1bj/B! BvJ
D#FH32f:xZINIJJsO9	$L  +%)&sC0Q|_%,,. *!) .c5)D
FYGPQf/ML4PQM-ahGMJ#C*RV_`af_ghjt  wF  GaKF1U8We4:2""9j9??AII#sSjb%%gr2((4U8((4a8H8H8SaWa JB++M2>!
 QT3?sAw#j/BQ		<!((*S+Z 3	7FDIIk<RSWTW<XY	#I;iy Q*O9WI? @Ra 	"AHQC.!	"IIKE Fj X& F  		s0   *R18R10R6+S6S	S		SS__main__script_startr   )returnrY   )r   rN   r<  sqlite3.Connection)r*   rN   r<  
list[dict])r   rN   r<  rN   )
rE   rN   r[   r"  r\   
str | Noner]   r?  r<  r>  )rE   rN   r\   r?  r]   r?  r<  ztuple[str | None, dict | None])rj   rN   r<  rN   )rM   rN   r]   r?  r   	list[str]r<  ztuple[str | None, str])rE   rN   r\   r?  r]   r?  r<  dict | None)r   rY   r   rY   r<  r?  )r   r=  rM   rN   r<  bool)
r   r=  r   rY   r   rN   r   rN   r<  None)r   r=  r   rY   r   rN   r   rN   r   r"  r<  rC  )r   rY   r   rN   r   rN   r   rA  r   rY   r<  rN   )r   rY   r<  r>  )rE   rN   r<  r?  )r   r=  r<  r"  )rH   )r   r=  r
  r"  r  r"  r  r"  r  r"  r  rN   r<  rC  )r(  r@  r<  r"  )1__doc__
__future__r   r}   _syspathinsertaria_loggingr   _get_logger_log
aria_auditr   _auditrU   r   r    r   rP   r$  r   r   r   pathlibr   r   r   r   r2   r6   rc   r   r~   r   r   r   r   r   r   r   r  r  r  r  r9  r   eventexitr(   r   r   <module>rR     s    #  		  $ % 2$% & 9 9 9 9 ' :;!
,,^2j $34l$&
&8$%
}@ zJJ~CHHT#((12,  r   