tweaks
This commit is contained in:
parent
f7e4e1766b
commit
9b47d95605
@ -48,6 +48,7 @@
|
||||
"P:\\movies\\Despicable Me 3 (2017)": 690230066,
|
||||
"P:\\movies\\Rush (2013)": 913581666,
|
||||
"P:\\movies\\The Nun II (2023)": 1057333481,
|
||||
"P:\\movies\\The Amityville Horror (2005)": 629562643,
|
||||
"P:\\movies\\SAW II (2005)": 628644928,
|
||||
"P:\\movies\\Fortress (2021)": 950706654,
|
||||
"P:\\movies\\Cocaine Bear (2023)": 923105028,
|
||||
@ -142,6 +143,7 @@
|
||||
"P:\\movies\\The Suicide Squad (2021)": 5220542826,
|
||||
"P:\\movies\\What We Do in the Shadows (2014)": 1324961025,
|
||||
"P:\\movies\\Pok\u00e9mon the Movie - Secrets of the Jungle (2020)": 2970730073,
|
||||
"P:\\movies\\Predators (2010)": 575097100,
|
||||
"P:\\movies\\First Shift (2024)": 862392953,
|
||||
"P:\\movies\\Christmas with the Campbells (2022)": 848536671,
|
||||
"P:\\movies\\Chicken Run - Dawn of the Nugget (2023)": 980150751,
|
||||
@ -314,7 +316,7 @@
|
||||
"P:\\movies\\Cats & Dogs - The Revenge of Kitty Galore (2010)": 794156004,
|
||||
"P:\\movies\\Instant Family (2018)": 1039796631,
|
||||
"P:\\movies\\Predator - Killer of Killers (2025)": 816946827,
|
||||
"P:\\movies\\Little Fockers (2010)": 523155307,
|
||||
"P:\\movies\\Little Fockers (2010)": 4277077072,
|
||||
"P:\\movies\\22 Jump Street (2014)": 856601465,
|
||||
"P:\\movies\\Bambi (1942)": 577460194,
|
||||
"P:\\movies\\Sleeping Beauty (1959)": 1258101653,
|
||||
@ -447,7 +449,7 @@
|
||||
"P:\\movies\\The Theory of Everything (2014)": 1014618750,
|
||||
"P:\\movies\\Hotel for the Holidays (2022)": 813498323,
|
||||
"P:\\movies\\Tomorrowland (2015)": 914578217,
|
||||
"P:\\movies\\Scott Pilgrim vs. the World (2010)": 5104233824,
|
||||
"P:\\movies\\Scott Pilgrim vs. the World (2010)": 2890731039,
|
||||
"P:\\movies\\Reign of the Supermen (2019)": 786280131,
|
||||
"P:\\movies\\The Bricklayer (2023)": 1060158754,
|
||||
"P:\\movies\\Sinners (2025)": 2230413475,
|
||||
@ -527,6 +529,7 @@
|
||||
"P:\\movies\\Bill and Ted's Bogus Journey (1991)": 1873085140,
|
||||
"P:\\movies\\Honey Don't! (2025)": 861699209,
|
||||
"P:\\movies\\Honey, I Blew Up the Kid (1992)": 788453313,
|
||||
"P:\\movies\\AJR - Somewhere in the sky (2026)": 15484584956,
|
||||
"P:\\movies\\Cyrano (2021)": 1186014065,
|
||||
"P:\\movies\\The Dark Tower (2017)": 960436348,
|
||||
"P:\\movies\\Good Boy! (2003)": 844713316,
|
||||
@ -613,10 +616,12 @@
|
||||
"P:\\movies\\Safe House (2012)": 787215510,
|
||||
"P:\\movies\\Pok\u00e9mon Detective Pikachu (2019)": 2033856759,
|
||||
"P:\\movies\\Superman - Red Son (2020)": 811801544,
|
||||
"P:\\movies\\Pirates of the Caribbean - Dead Man's Chest (2006)": 3785474444,
|
||||
"P:\\movies\\High Rollers (2025)": 976326920,
|
||||
"P:\\movies\\Pirates of the Caribbean - Dead Man's Chest (2006)": 5677959143,
|
||||
"P:\\movies\\Waitress (2007)": 1008081415,
|
||||
"P:\\movies\\Chef (2014)": 854812021,
|
||||
"P:\\movies\\Spider-Man (2002)": 1979695887,
|
||||
"P:\\movies\\Five Nights at Freddy's 2 (2025)": 1003470675,
|
||||
"P:\\movies\\Patch Adams (1998)": 1017725633,
|
||||
"P:\\movies\\The Conjuring - Last Rites (2025)": 1307715145,
|
||||
"P:\\movies\\Theater Camp (2023)": 891947338,
|
||||
@ -653,7 +658,7 @@
|
||||
"P:\\movies\\Transporter 3 (2008)": 629053239,
|
||||
"P:\\movies\\Red Rocket (2021)": 1250775533,
|
||||
"P:\\movies\\Chaos (2005)": 848357261,
|
||||
"P:\\movies\\Superman (2025)": 13124179581,
|
||||
"P:\\movies\\Superman (2025)": 6208410119,
|
||||
"P:\\movies\\Layer Cake (2004)": 682551661,
|
||||
"P:\\movies\\Once Upon a Christmas Miracle (2018)": 819838950,
|
||||
"P:\\movies\\You Hurt My Feelings (2023)": 895680655,
|
||||
@ -665,7 +670,7 @@
|
||||
"P:\\movies\\Badland Hunters (2024)": 1049775793,
|
||||
"P:\\movies\\Gangster No. 1 (2000)": 990977285,
|
||||
"P:\\movies\\Am I Racist! (2024)": 837441227,
|
||||
"P:\\movies\\Ferris Bueller's Day Off (1986)": 5980268040,
|
||||
"P:\\movies\\Ferris Bueller's Day Off (1986)": 3533513139,
|
||||
"P:\\movies\\Margin Call (2011)": 734058146,
|
||||
"P:\\movies\\When You Finish Saving the World (2023)": 844223076,
|
||||
"P:\\movies\\The Nines (2007)": 894876710,
|
||||
@ -946,7 +951,7 @@
|
||||
"P:\\movies\\Ponyo (2008)": 5677514449,
|
||||
"P:\\movies\\BASEketball (1998)": 994729312,
|
||||
"P:\\movies\\Hunter x Hunter - Phantom Rouge (2013)": 864950409,
|
||||
"P:\\movies\\Pirates of the Caribbean - At World's End (2007)": 4117617111,
|
||||
"P:\\movies\\Pirates of the Caribbean - At World's End (2007)": 6961369007,
|
||||
"P:\\movies\\Detective Knight - Rogue (2022)": 1012614253,
|
||||
"P:\\movies\\Cliffhanger (1993)": 786895598,
|
||||
"P:\\movies\\Slap Shot (1977)": 910196582,
|
||||
@ -1003,9 +1008,11 @@
|
||||
"P:\\movies\\Waterworld (1995)": 1069371604,
|
||||
"P:\\movies\\Longlegs (2024)": 835974578,
|
||||
"P:\\movies\\Dual (2022)": 909646357,
|
||||
"P:\\movies\\xXx - Return of Xander Cage (2017)": 9537340240,
|
||||
"P:\\movies\\xXx - Return of Xander Cage (2017)": 3563512464,
|
||||
"P:\\movies\\Robin Williams - Come Inside My Mind (2018)": 1021217913,
|
||||
"P:\\movies\\Brave (2012)": 629765937,
|
||||
"P:\\movies\\Batman and Harley Quinn (2017)": 575516986,
|
||||
"P:\\movies\\Hunting Season (2025)": 966911333,
|
||||
"P:\\movies\\Last Flag Flying (2017)": 947531807,
|
||||
"P:\\movies\\A Long Way Down (2014)": 792782735,
|
||||
"P:\\movies\\Shin Godzilla (2016)": 1951714489,
|
||||
@ -1131,7 +1138,7 @@
|
||||
"P:\\movies\\Ghostbusters (1984)": 1756467527,
|
||||
"P:\\movies\\This Means War (2012)": 734559917,
|
||||
"P:\\movies\\Reunion (2024)": 909882084,
|
||||
"P:\\movies\\The Losers (2010)": 5151113302,
|
||||
"P:\\movies\\The Losers (2010)": 2964760220,
|
||||
"P:\\movies\\Fighting with My Family (2019)": 964548802,
|
||||
"P:\\movies\\Strays (2023)": 910698288,
|
||||
"P:\\movies\\Hansan - Rising Dragon (2022)": 1253568599,
|
||||
@ -1235,19 +1242,19 @@
|
||||
"P:\\movies\\A Fantastic Fear of Everything (2012)": 786055829,
|
||||
"P:\\movies\\Green Room (2015)": 737645792,
|
||||
"P:\\movies\\Rise of the Guardians (2012)": 733537206,
|
||||
"P:\\movies\\The Roundup (2022)": 5716789610,
|
||||
"P:\\movies\\The Roundup (2022)": 2397207186,
|
||||
"P:\\movies\\The Great Wall (2016)": 794233234,
|
||||
"P:\\movies\\Green Day - 20 Years of American Idiot (2024)": 1064467825,
|
||||
"P:\\movies\\Sorry to Bother You (2018)": 981546629,
|
||||
"P:\\movies\\Merry In-Laws (2012)": 849094480,
|
||||
"P:\\movies\\Black Clover - Sword of the Wizard King (2023)": 2792853722,
|
||||
"P:\\movies\\Rocky V (1990)": 683775581,
|
||||
"P:\\movies\\Starship Troopers (1997)": 7366521230,
|
||||
"P:\\movies\\Starship Troopers (1997)": 5339159761,
|
||||
"P:\\movies\\Megamind (2010)": 629041367,
|
||||
"P:\\movies\\Mad God (2021)": 809156360,
|
||||
"P:\\movies\\The Other Zoey (2023)": 885187485,
|
||||
"P:\\movies\\The Hunt (2020)": 866933622,
|
||||
"P:\\movies\\Pirates of the Caribbean - Dead Men Tell No Tales (2017)": 3171926078,
|
||||
"P:\\movies\\Pirates of the Caribbean - Dead Men Tell No Tales (2017)": 3600745878,
|
||||
"P:\\movies\\Spanglish (2004)": 996868451,
|
||||
"P:\\movies\\Holes (2003)": 1674984992,
|
||||
"P:\\movies\\Big Trouble (2002)": 762439934,
|
||||
@ -1486,7 +1493,7 @@
|
||||
"P:\\movies\\I am Legend (2007)": 472399898,
|
||||
"P:\\movies\\Seven Psychopaths (2012)": 787315239,
|
||||
"P:\\movies\\Ron's Gone Wrong (2021)": 1027827258,
|
||||
"P:\\movies\\Paradise Records (2025)": 2155834535,
|
||||
"P:\\movies\\Paradise Records (2025)": 1051339618,
|
||||
"P:\\movies\\The Nun (2018)": 876835728,
|
||||
"P:\\movies\\Mars Attacks! (1996)": 974084293,
|
||||
"P:\\movies\\How the Grinch Stole Christmas (2000)": 3268490830,
|
||||
@ -1579,8 +1586,9 @@
|
||||
"P:\\movies\\The Phoenician Scheme (2025)": 2661907395,
|
||||
"P:\\movies\\Cinderella (2021)": 1087262833,
|
||||
"P:\\movies\\The Lego Movie 2 The Second Part (2019)": 950172844,
|
||||
"P:\\movies\\Meet the Fockers (2004)": 1495880216,
|
||||
"P:\\movies\\Meet the Fockers (2004)": 5949252474,
|
||||
"P:\\movies\\The Bad Batch (2017)": 926871424,
|
||||
"P:\\movies\\Forever Young (1992)": 977859032,
|
||||
"P:\\movies\\Halloweentown (1998)": 810513129,
|
||||
"P:\\movies\\Silent Night (2023)": 1003667495,
|
||||
"P:\\movies\\Bridget Jones's Diary (2001)": 925436159,
|
||||
@ -1655,7 +1663,7 @@
|
||||
"P:\\movies\\Vacation (2015)": 882534348,
|
||||
"P:\\movies\\Canadian Bacon (1995)": 915905234,
|
||||
"P:\\movies\\Blue Beetle (2023)": 1949820350,
|
||||
"P:\\movies\\The Baker (2023)": 5497792586,
|
||||
"P:\\movies\\The Baker (2023)": 816561611,
|
||||
"P:\\movies\\Jurassic Park (1993)": 793306783,
|
||||
"P:\\movies\\Code 3 (2025)": 962773414,
|
||||
"P:\\movies\\Memory (2022)": 1098424408,
|
||||
@ -1685,7 +1693,7 @@
|
||||
"P:\\movies\\S.W.A.T. (2019)": 1056909910,
|
||||
"P:\\movies\\The Persian Version (2023)": 1036376746,
|
||||
"P:\\movies\\Bambi II (2006)": 643557348,
|
||||
"P:\\movies\\Pirates of the Caribbean - The Curse of the Black Pearl (2003)": 6749680201,
|
||||
"P:\\movies\\Pirates of the Caribbean - The Curse of the Black Pearl (2003)": 6164584510,
|
||||
"P:\\movies\\Ordinary World (2016)": 1414665447,
|
||||
"P:\\movies\\Bad Boys for Life (2020)": 1193361085,
|
||||
"P:\\movies\\Pok\u00e9mon - Lucario and the Mystery of Mew (2005)": 2253435788,
|
||||
@ -1769,7 +1777,7 @@
|
||||
"P:\\movies\\The Bikeriders (2024)": 1121777975,
|
||||
"P:\\movies\\Stuber (2019)": 861709290,
|
||||
"P:\\movies\\John Wick - Chapter 4 (2023)": 4680013949,
|
||||
"P:\\movies\\Pirates of the Caribbean - On Stranger Tides (2011)": 2317926572,
|
||||
"P:\\movies\\Pirates of the Caribbean - On Stranger Tides (2011)": 4297016060,
|
||||
"P:\\movies\\Ghost (1990)": 872499373,
|
||||
"P:\\movies\\Confessions of a Sociopathic Social Climber (2005)": 820262358,
|
||||
"P:\\movies\\Fatherhood (2021)": 1050118813,
|
||||
@ -2135,6 +2143,7 @@
|
||||
"P:\\movies\\Beauty and the Beast - Extended Edition (1991)": 575996197,
|
||||
"P:\\movies\\R.I.P.D. 2 - Rise of the Damned (2022)": 921792650,
|
||||
"P:\\movies\\Couples Retreat (2009)": 787204478,
|
||||
"P:\\movies\\The Wedding Date (2005)": 857810936,
|
||||
"P:\\movies\\The Girl Next Door UNRATED (2004)": 787262062,
|
||||
"P:\\movies\\Downey Wrote That (2025)": 643153092,
|
||||
"P:\\movies\\The Nutty Professor (1996)": 681729259,
|
||||
@ -2185,7 +2194,7 @@
|
||||
"P:\\movies\\Jurassic World - Fallen Kingdom (2018)": 1158321969,
|
||||
"P:\\movies\\Moonlight (2016)": 851155837,
|
||||
"P:\\movies\\Barbarella (1968)": 943578060,
|
||||
"P:\\movies\\Small Soldiers (1998)": 5094073349,
|
||||
"P:\\movies\\Small Soldiers (1998)": 3036317509,
|
||||
"P:\\movies\\Gran Torino (2008)": 788767144,
|
||||
"P:\\movies\\Enchanted (2007)": 955502916,
|
||||
"P:\\movies\\Nate Bargatze - Hello World (2023)": 587515341,
|
||||
@ -2217,7 +2226,7 @@
|
||||
"P:\\movies\\Ride 'Em Cowboy (1942)": 826176530,
|
||||
"P:\\movies\\South Park the Streaming Wars (2022)": 462635235,
|
||||
"P:\\movies\\The Quintessential Quintuplets Movie (2022)": 2671283313,
|
||||
"P:\\movies\\John Wick - Chapter 2 (2017)": 6452174217,
|
||||
"P:\\movies\\John Wick - Chapter 2 (2017)": 2910556976,
|
||||
"P:\\movies\\Hitman Agent 47 (2015)": 586386449,
|
||||
"P:\\movies\\Paul (2011)": 731417836,
|
||||
"P:\\movies\\Butter (2012)": 809373699,
|
||||
@ -2384,7 +2393,7 @@
|
||||
"P:\\movies\\Halloweentown II - Kalabar's Revenge (2001)": 1052744699,
|
||||
"P:\\movies\\The Survivors (1983)": 883588130,
|
||||
"P:\\movies\\A Bad Moms Christmas (2017)": 800570325,
|
||||
"P:\\movies\\Meet the Parents (2000)": 2131659488,
|
||||
"P:\\movies\\Meet the Parents (2000)": 6321750057,
|
||||
"P:\\movies\\Slayers (2022)": 849805353,
|
||||
"P:\\movies\\The Good Nurse (2022)": 840206859,
|
||||
"P:\\movies\\The Death and Life of Bobby Z (2007)": 788294208,
|
||||
@ -2478,7 +2487,7 @@
|
||||
"P:\\movies\\Spider-Man - Far From Home (2019)": 1838406206,
|
||||
"P:\\movies\\Jack Reacher (2012)": 2051597479,
|
||||
"P:\\movies\\The Meg (2018)": 1019483886,
|
||||
"P:\\movies\\Violent Night (2022)": 5106244959,
|
||||
"P:\\movies\\Violent Night (2022)": 1906909883,
|
||||
"P:\\movies\\House of Gucci (2021)": 1519261888,
|
||||
"P:\\movies\\Mulholland Falls (1996)": 852069037,
|
||||
"P:\\movies\\The Golden Child (1986)": 833761735,
|
||||
@ -2501,7 +2510,7 @@
|
||||
"P:\\movies\\The Good, the Bart, and the Loki (2021)": 43984236,
|
||||
"P:\\movies\\Clerks III (2022)": 963907340,
|
||||
"P:\\movies\\M3GAN 2.0 (2025)": 1158862629,
|
||||
"P:\\movies\\Belle (2021)": 6192364946,
|
||||
"P:\\movies\\Belle (2021)": 1967787278,
|
||||
"P:\\movies\\Minamata (2020)": 1104986233,
|
||||
"P:\\movies\\Alive (1993)": 1216643767,
|
||||
"P:\\movies\\The Princess and the Frog (2009)": 734834721,
|
||||
@ -2510,7 +2519,6 @@
|
||||
"P:\\movies\\Inside Llewyn Davis (2013)": 849607966,
|
||||
"P:\\movies\\The Addams Family 2 (2021)": 892624431,
|
||||
"P:\\movies\\She's Out of My League (2010)": 682549102,
|
||||
"P:\\movies\\AJR - Somewhere in the sky (2025)": 2919401404,
|
||||
"P:\\movies\\Minions & More 1 (2022)": 465025396,
|
||||
"P:\\movies\\Blade Runner 2049 (2017)": 2693638088,
|
||||
"P:\\movies\\Twilight Saga (2008)": 733993564,
|
||||
@ -2607,7 +2615,7 @@
|
||||
"P:\\movies\\Youngblood (1986)": 978057487,
|
||||
"P:\\movies\\Inglourious Basterds (2009)": 2855779029,
|
||||
"P:\\movies\\Black or White (2014)": 911771671,
|
||||
"P:\\movies\\AJR - The Maybe Man Immersive Concert Experience (2024)": 4590528226,
|
||||
"P:\\movies\\AJR - The Maybe Man Immersive Concert Experience (2024)": 4591719568,
|
||||
"P:\\movies\\Animal House (1978)": 787489930,
|
||||
"P:\\movies\\Cinderella (2015)": 1763561462,
|
||||
"P:\\movies\\I'll Be Home for Christmas (1998)": 769320107,
|
||||
@ -2630,7 +2638,7 @@
|
||||
"P:\\movies\\The Master of Disguise (2002)": 1343424184,
|
||||
"P:\\movies\\DodgeBall - A True Underdog Story (2004)": 787609348,
|
||||
"P:\\movies\\The Emperor's New Groove (2000)": 726824390,
|
||||
"P:\\movies\\John Wick - Chapter 3 - Parabellum (2019)": 7853540695,
|
||||
"P:\\movies\\John Wick - Chapter 3 - Parabellum (2019)": 3857527829,
|
||||
"P:\\movies\\Aloha (2015)": 849543660,
|
||||
"P:\\movies\\Thumbsucker (2005)": 925537380,
|
||||
"P:\\movies\\Ghosted (2023)": 2318216001,
|
||||
@ -2705,6 +2713,7 @@
|
||||
"P:\\movies\\Palm Springs (2020)": 1609086346,
|
||||
"P:\\movies\\Captain America - The First Avenger (2011)": 1713875628,
|
||||
"P:\\movies\\Clear History (2013)": 791420953,
|
||||
"P:\\movies\\Interstate 60 (2002)": 1079307887,
|
||||
"P:\\movies\\Jeff Dunham - Me the People (2022)": 404337420,
|
||||
"P:\\movies\\Scary Movie 2 (2001)": 794281384,
|
||||
"P:\\movies\\About My Father (2023)": 1609106023,
|
||||
|
||||
@ -1,322 +1,322 @@
|
||||
{
|
||||
"P:\\tv\\1883": 4514294832,
|
||||
"P:\\tv\\1923": 22125507023,
|
||||
"P:\\tv\\3 Body Problem": 11369334730,
|
||||
"P:\\tv\\30 Rock (2006)": 81412969909,
|
||||
"P:\\tv\\Abbott Elementary (2021)": 24595462535,
|
||||
"P:\\tv\\Adults (2025)": 6845585714,
|
||||
"P:\\tv\\Adventuring Academy": 62196997373,
|
||||
"P:\\tv\\Agatha All Along": 3411637969,
|
||||
"P:\\tv\\Alien - Earth (2025)": 2926145405,
|
||||
"P:\\tv\\Amazing Stories (2020)": 4281304451,
|
||||
"P:\\tv\\American Gods (2017)": 43921706762,
|
||||
"P:\\tv\\American Horror Story": 142468660014,
|
||||
"P:\\tv\\Andor (2022)": 25679584728,
|
||||
"P:\\tv\\Arcane (2021)": 19588567847,
|
||||
"P:\\tv\\Assembly Required (2021)": 5737519036,
|
||||
"P:\\tv\\Avenue 5": 12572813494,
|
||||
"P:\\tv\\Bad Monkey": 7767595411,
|
||||
"P:\\tv\\Ballers": 13002096756,
|
||||
"P:\\tv\\Band of Brothers (2001)": 15129362120,
|
||||
"P:\\tv\\Banshee (2013)": 25030541772,
|
||||
"P:\\tv\\Barry": 31934844666,
|
||||
"P:\\tv\\BattleBots": 61,
|
||||
"P:\\tv\\BattleBots (2015)": 69,
|
||||
"P:\\tv\\Being Human (2011)": 66311454464,
|
||||
"P:\\tv\\Belgravia - The Next Chapter": 8340040939,
|
||||
"P:\\tv\\Below Deck": 47516712212,
|
||||
"P:\\tv\\Below Deck Down Under (2022)": 36006759742,
|
||||
"P:\\tv\\Below Deck Mediterranean": 39902249615,
|
||||
"P:\\tv\\Below Deck Sailing Yacht": 12706704039,
|
||||
"P:\\tv\\Better Call Saul": 31152560439,
|
||||
"P:\\tv\\Billions": 31141419259,
|
||||
"P:\\tv\\Billy the Kid": 44803721006,
|
||||
"P:\\tv\\Black Bird (2022)": 5893929480,
|
||||
"P:\\tv\\Black Sails (2014)": 11356486450,
|
||||
"P:\\tv\\Brooklyn Nine Nine": 45722673163,
|
||||
"P:\\tv\\Bupkis": 13034439710,
|
||||
"P:\\tv\\Canada's Drag Race": 103586850759,
|
||||
"P:\\tv\\Canada's Drag Race vs The World": 7844155647,
|
||||
"P:\\tv\\Catch-22": 7113496871,
|
||||
"P:\\tv\\Chad Powers (2025)": 2474659236,
|
||||
"P:\\tv\\Chilling Adventures of Sabrina (2018)": 23147355371,
|
||||
"P:\\tv\\Chuck": 32193192829,
|
||||
"P:\\tv\\Citadel": 2339699246,
|
||||
"P:\\tv\\Citadel - Diana": 13304679453,
|
||||
"P:\\tv\\Cobra Kai": 39761471967,
|
||||
"P:\\tv\\Continuum (2012)": 29352883496,
|
||||
"P:\\tv\\Countdown (2025)": 8935252687,
|
||||
"P:\\tv\\Counterpart": 4875616955,
|
||||
"P:\\tv\\Creature Commandos (2024)": 2331424358,
|
||||
"P:\\tv\\Crowd Control": 9644641207,
|
||||
"P:\\tv\\Cyberpunk - Edgerunners (2022)": 11313875182,
|
||||
"P:\\tv\\Daredevil - Born Again (2025)": 7647367391,
|
||||
"P:\\tv\\Dark Side of the Ring": 11863132534,
|
||||
"P:\\tv\\Dateline NBC (1992)": 19267231607,
|
||||
"P:\\tv\\Death and Other Details": 17844763765,
|
||||
"P:\\tv\\Detroiters (2017)": 33750584701,
|
||||
"P:\\tv\\Dimension 20": 557729281243,
|
||||
"P:\\tv\\Dimension 20's Adventuring Party": 12002285238,
|
||||
"P:\\tv\\Dirk Gently's Holistic Detective Agency (2016)": 11935610182,
|
||||
"P:\\tv\\Dirty Laundry": 38036591078,
|
||||
"P:\\tv\\Doctor Who (2005)": 5820708419,
|
||||
"P:\\tv\\Dopesick": 2571994785,
|
||||
"P:\\tv\\DOTA - Dragon's Blood (2021)": 12538510766,
|
||||
"P:\\tv\\Dracula (2020)": 2147285239,
|
||||
"P:\\tv\\Dune - Prophecy": 3330003290,
|
||||
"P:\\tv\\Dungeons & Dragons": 6660128393,
|
||||
"P:\\tv\\Dwight in Shining Armor": 75,
|
||||
"P:\\tv\\English Teacher": 7603165476,
|
||||
"P:\\tv\\Euphoria": 40925172559,
|
||||
"P:\\tv\\Extraordinary": 6934203888,
|
||||
"P:\\tv\\Extrapolations": 6155965724,
|
||||
"P:\\tv\\Face Off (2011)": 83155672195,
|
||||
"P:\\tv\\Fallen (2024)": 4161867429,
|
||||
"P:\\tv\\Fallout": 19686023936,
|
||||
"P:\\tv\\Fargo (2014)": 93792752129,
|
||||
"P:\\tv\\Father Brown": 18896564477,
|
||||
"P:\\tv\\Fired on Mars (2023)": 3590992124,
|
||||
"P:\\tv\\Firefly (2002)": 7517428895,
|
||||
"P:\\tv\\From Dusk Till Dawn - The Series (2014)": 5360771338,
|
||||
"P:\\tv\\Galavant": 12147863291,
|
||||
"P:\\tv\\Game Changer": 38317757866,
|
||||
"P:\\tv\\Game Changers (2024)": 5880504271,
|
||||
"P:\\tv\\Game Of Thrones": 119681469870,
|
||||
"P:\\tv\\Gastronauts": 9365810750,
|
||||
"P:\\tv\\Gen V (2023)": 16871757804,
|
||||
"P:\\tv\\Ghosts (2019)": 40703143881,
|
||||
"P:\\tv\\Ghosts (2021)": 4574333812,
|
||||
"P:\\tv\\Goosebumps (2023)": 8257419062,
|
||||
"P:\\tv\\Gordon Ramsay's Food Stars (2023)": 6344621632,
|
||||
"P:\\tv\\Government Cheese (2025)": 15970704500,
|
||||
"P:\\tv\\Gravity Falls": 31900305156,
|
||||
"P:\\tv\\Halo": 6961206915,
|
||||
"P:\\tv\\Harley and the Davidsons": 76,
|
||||
"P:\\tv\\Harley Quinn": 20857796821,
|
||||
"P:\\tv\\Harry Potter - Wizards of Baking (2024)": 23545641052,
|
||||
"P:\\tv\\Haunted Hotel (2025)": 4735071992,
|
||||
"P:\\tv\\Hawkeye": 13524278345,
|
||||
"P:\\tv\\Hazbin Hotel (2024)": 10906489515,
|
||||
"P:\\tv\\Hero Inside (2023)": 7372329680,
|
||||
"P:\\tv\\High Potential": 24339309461,
|
||||
"P:\\tv\\Hitmen (2020)": 12274410846,
|
||||
"P:\\tv\\Home Economics": 14315967074,
|
||||
"P:\\tv\\Home Improvement 1991": 48878774505,
|
||||
"P:\\tv\\House of Guinness (2025)": 5444928896,
|
||||
"P:\\tv\\House of the Dragon": 23959073249,
|
||||
"P:\\tv\\iCarly (2021)": 19966043984,
|
||||
"P:\\tv\\Impractical Jokers": 13357380400,
|
||||
"P:\\tv\\In the Dark (2019)": 2555891397,
|
||||
"P:\\tv\\Ink Master": 23329086486,
|
||||
"P:\\tv\\Interior Chinatown": 3167640001,
|
||||
"P:\\tv\\Invincible (2021)": 19742824176,
|
||||
"P:\\tv\\Ironheart (2025)": 3153557870,
|
||||
"P:\\tv\\Its Always Sunny in Philadelphia": 84650830434,
|
||||
"P:\\tv\\Jentry Chau vs. the Underworld (2024)": 1406237358,
|
||||
"P:\\tv\\Junior Taskmaster (2024)": 4133620030,
|
||||
"P:\\tv\\Jury Duty": 8010062372,
|
||||
"P:\\tv\\Kaos": 5164057710,
|
||||
"P:\\tv\\Kevin Can F-k Himself": 11614889793,
|
||||
"P:\\tv\\Killer Cakes": 3673781461,
|
||||
"P:\\tv\\Kim's Convenience": 30475634673,
|
||||
"P:\\tv\\Kitchen Nightmares UK": 11563663098,
|
||||
"P:\\tv\\Kitchen Nightmares US": 56092851597,
|
||||
"P:\\tv\\Knuckles": 2140786440,
|
||||
"P:\\tv\\Krypton (2018)": 10875524680,
|
||||
"P:\\tv\\Landman (2024)": 35220290035,
|
||||
"P:\\tv\\Last Man Standing": 49393251846,
|
||||
"P:\\tv\\Lawmen - Bass Reeves (2023)": 5363156538,
|
||||
"P:\\tv\\Lessons in Chemistry (2023)": 5485801173,
|
||||
"P:\\tv\\Letterkenny": 63,
|
||||
"P:\\tv\\Life After People (2009)": 45628647899,
|
||||
"P:\\tv\\Loki": 20082144632,
|
||||
"P:\\tv\\Love Island (US) (2019)": 20699120877,
|
||||
"P:\\tv\\Love, Death & Robots (2019)": 8204860116,
|
||||
"P:\\tv\\Lucky Hank": 7336222432,
|
||||
"P:\\tv\\Ludwig (2024)": 2670615425,
|
||||
"P:\\tv\\Made For Love (2021)": 2211136772,
|
||||
"P:\\tv\\Make Some Noise": 25555591381,
|
||||
"P:\\tv\\Man Down (2013)": 5077144151,
|
||||
"P:\\tv\\Married at First Sight (2014)": 30275711911,
|
||||
"P:\\tv\\Married... with Children (1987)": 64228823786,
|
||||
"P:\\tv\\Marvel's The Punisher (2017)": 32242478897,
|
||||
"P:\\tv\\Matlock (2024)": 34470939613,
|
||||
"P:\\tv\\Mayor of Kingstown (2021)": 65464041666,
|
||||
"P:\\tv\\Mighty Nein (2025)": 6138965943,
|
||||
"P:\\tv\\MobLand (2025)": 6622179548,
|
||||
"P:\\tv\\Modern Family": 82788065200,
|
||||
"P:\\tv\\Monarch Legacy of Monsters": 18371826949,
|
||||
"P:\\tv\\Monet's Slumber Party": 8253206091,
|
||||
"P:\\tv\\Moon Knight": 10976093361,
|
||||
"P:\\tv\\Mr. & Mrs. Smith (2024)": 5316681916,
|
||||
"P:\\tv\\Murder She Wrote": 12095973826,
|
||||
"P:\\tv\\Murderbot (2025)": 18338040970,
|
||||
"P:\\tv\\Mythic Quest": 16965795814,
|
||||
"P:\\tv\\New Girl": 40676856398,
|
||||
"P:\\tv\\Nobody Wants This": 11516933757,
|
||||
"P:\\tv\\Obi-Wan Kenobi": 13867986923,
|
||||
"P:\\tv\\One More Time (2024)": 6434473461,
|
||||
"P:\\tv\\Only Murders in the Building (2021)": 2379838148,
|
||||
"P:\\tv\\Our Flag Means Death": 2107045664,
|
||||
"P:\\tv\\Outlander": 27364180668,
|
||||
"P:\\tv\\Over the Garden Wall": 2937573633,
|
||||
"P:\\tv\\Pantheon": 13397374449,
|
||||
"P:\\tv\\Paradise (2025)": 8024209737,
|
||||
"P:\\tv\\Parks and Recreation": 37277190974,
|
||||
"P:\\tv\\Parlor Room": 12022280605,
|
||||
"P:\\tv\\Passion for punchlines": 75514795,
|
||||
"P:\\tv\\Peacemaker (2022)": 13199970800,
|
||||
"P:\\tv\\Percy Jackson and the Olympians": 3558450335,
|
||||
"P:\\tv\\Platonic (2023)": 17488146510,
|
||||
"P:\\tv\\Pok\u00e9mon Concierge (2023)": 1134616527,
|
||||
"P:\\tv\\Poppa\u2019s House": 13794748297,
|
||||
"P:\\tv\\Power (2014)": 20414619656,
|
||||
"P:\\tv\\Quantum Leap (1989)": 39284023472,
|
||||
"P:\\tv\\Quantum Leap 2022": 8902776416,
|
||||
"P:\\tv\\Quiet On Set - The Dark Side Of Kids TV": 12191520028,
|
||||
"P:\\tv\\Raised by wolves": 9720677524,
|
||||
"P:\\tv\\Reacher (2022)": 17521873037,
|
||||
"P:\\tv\\Resident Alien (2021)": 17522605407,
|
||||
"P:\\tv\\Rick and Morty": 31672318625,
|
||||
"P:\\tv\\Royal Pains (2009)": 1247586112,
|
||||
"P:\\tv\\Running Man": 10279755878,
|
||||
"P:\\tv\\Rupaul's Drag Race": 57149739065,
|
||||
"P:\\tv\\Rupaul's Drag Race All Stars": 60579323023,
|
||||
"P:\\tv\\RuPaul's Drag Race Down Under": 27454793482,
|
||||
"P:\\tv\\Rupaul's Drag Race UK": 110914388896,
|
||||
"P:\\tv\\Rupaul's Drag Race Vegas Revue": 2532474468,
|
||||
"P:\\tv\\Rupauls Drag Race UK vs The World": 35504142221,
|
||||
"P:\\tv\\SAS Rogue Heroes (2022)": 10733559643,
|
||||
"P:\\tv\\Saving Hope": 33116225358,
|
||||
"P:\\tv\\Scenes from a Marriage (US)": 12493986505,
|
||||
"P:\\tv\\Schitt's Creek": 9325109901,
|
||||
"P:\\tv\\Schmigadoon!": 6206632733,
|
||||
"P:\\tv\\SCORPION": 54081802764,
|
||||
"P:\\tv\\Secret Celebrity RuPaul's Drag Race": 4211193920,
|
||||
"P:\\tv\\Secret Level": 2810124465,
|
||||
"P:\\tv\\See": 12316511887,
|
||||
"P:\\tv\\Selfie": 5013734266,
|
||||
"P:\\tv\\Severance": 15044806873,
|
||||
"P:\\tv\\She-Hulk Attorney at Law": 10233633417,
|
||||
"P:\\tv\\Shetland": 18537045340,
|
||||
"P:\\tv\\Shifting Gears (2025)": 12649531141,
|
||||
"P:\\tv\\Shoresy": 9701736850,
|
||||
"P:\\tv\\Shrinking (2023)": 17293593983,
|
||||
"P:\\tv\\Sh\u014dgun": 20899988683,
|
||||
"P:\\tv\\Silicon Valley (2014)": 63657428121,
|
||||
"P:\\tv\\Silo (2023)": 12897630564,
|
||||
"P:\\tv\\Sirens (2025)": 4246622090,
|
||||
"P:\\tv\\Smartypants": 15959708127,
|
||||
"P:\\tv\\Smiling Friends": 5633340834,
|
||||
"P:\\tv\\Solar Opposites": 1138214210,
|
||||
"P:\\tv\\Son of Zorn (2016)": 6780978712,
|
||||
"P:\\tv\\South Park": 70261225261,
|
||||
"P:\\tv\\Spartacus": 75639017886,
|
||||
"P:\\tv\\Special Ops Lioness": 9765393961,
|
||||
"P:\\tv\\Squid Game (2021)": 22082475135,
|
||||
"P:\\tv\\St. Denis Medical (2024)": 18704263469,
|
||||
"P:\\tv\\Star Strek Strange New Worlds": 13781151928,
|
||||
"P:\\tv\\Star Trek Lower Decks": 33090597113,
|
||||
"P:\\tv\\Star Wars - Skeleton Crew (2024)": 2940779001,
|
||||
"P:\\tv\\Stargirl": 9507100884,
|
||||
"P:\\tv\\Station Eleven": 2708694925,
|
||||
"P:\\tv\\Stranger Things (2016)": 64934698827,
|
||||
"P:\\tv\\Suits LA (2025)": 22274831381,
|
||||
"P:\\tv\\Superman and Lois": 44881535930,
|
||||
"P:\\tv\\Supernatural": 377851589424,
|
||||
"P:\\tv\\Sweetpea": 2706241673,
|
||||
"P:\\tv\\Swimming with Sharks": 4426141798,
|
||||
"P:\\tv\\Taboo (2017)": 19309841226,
|
||||
"P:\\tv\\Taskmaster": 142193364333,
|
||||
"P:\\tv\\Taskmaster (CA) (2022)": 2431664380,
|
||||
"P:\\tv\\Taskmaster (NZ)": 71323320898,
|
||||
"P:\\tv\\Taskmaster - Champion of Champions": 2700754514,
|
||||
"P:\\tv\\Taskmaster AU": 20527610746,
|
||||
"P:\\tv\\Taylor (2025)": 2621206209,
|
||||
"P:\\tv\\Ted (2024)": 3024624414,
|
||||
"P:\\tv\\Ted Lasso (2020)": 52046307136,
|
||||
"P:\\tv\\Terminator Zero": 3384699699,
|
||||
"P:\\tv\\The 10th Kingdom (2000)": 14174589505,
|
||||
"P:\\tv\\The Amazing Digital Circus (2023)": 4739070191,
|
||||
"P:\\tv\\The Bachelor": 40368931577,
|
||||
"P:\\tv\\The Bachelorette": 9927266246,
|
||||
"P:\\tv\\The Bear (2022)": 43665628138,
|
||||
"P:\\tv\\The Big Door Prize": 2314902686,
|
||||
"P:\\tv\\The Bondsman (2025)": 3112664353,
|
||||
"P:\\tv\\The Book of Boba Fett": 12039417291,
|
||||
"P:\\tv\\The Boys": 68010010167,
|
||||
"P:\\tv\\The Chosen (2019)": 54241850899,
|
||||
"P:\\tv\\The Closer": 47449608535,
|
||||
"P:\\tv\\The Consultant (2023)": 74,
|
||||
"P:\\tv\\The Continental (2023)": 1920206807,
|
||||
"P:\\tv\\The Day of the Jackal (2024)": 17787097381,
|
||||
"P:\\tv\\The Dragon Dentist": 11317084093,
|
||||
"P:\\tv\\The Drew Carey Show (1995)": 70,
|
||||
"P:\\tv\\The Edge of Sleep": 1358235145,
|
||||
"P:\\tv\\The Eternaut": 17178505929,
|
||||
"P:\\tv\\The Falcon and The Winter Soldier (2021)": 11657055937,
|
||||
"P:\\tv\\The Fall of Diddy (2025)": 2431035593,
|
||||
"P:\\tv\\The Fall of the House of Usher (2023)": 16454192941,
|
||||
"P:\\tv\\The Forsytes (2025)": 4034792830,
|
||||
"P:\\tv\\The Franchise (2024)": 2981270395,
|
||||
"P:\\tv\\The Gentlemen (2024)": 5224500371,
|
||||
"P:\\tv\\The Gilded Age": 90505242840,
|
||||
"P:\\tv\\The Goes Wrong Show (2019)": 3676343887,
|
||||
"P:\\tv\\The Good Lord Bird (2020)": 5619421375,
|
||||
"P:\\tv\\The Great (2020)": 22361386693,
|
||||
"P:\\tv\\The Great British Bake Off": 78,
|
||||
"P:\\tv\\The IT Crowd (2006)": 9239572772,
|
||||
"P:\\tv\\The Journal of the Mysterious Creatures (2019)": 92,
|
||||
"P:\\tv\\The Last of Us": 30545352719,
|
||||
"P:\\tv\\The Legend of Vox Machina": 25197294503,
|
||||
"P:\\tv\\The Lord of the Rings - The Rings of Power": 12834498889,
|
||||
"P:\\tv\\The Mandalorian": 36487773789,
|
||||
"P:\\tv\\The Morning Show": 94311701751,
|
||||
"P:\\tv\\The Newsroom": 27756667258,
|
||||
"P:\\tv\\The Now": 836886747,
|
||||
"P:\\tv\\The Offer": 9070667475,
|
||||
"P:\\tv\\The Office (US)": 161867626607,
|
||||
"P:\\tv\\The Old Man (2022)": 26139845941,
|
||||
"P:\\tv\\The Originals (2013)": 72912846985,
|
||||
"P:\\tv\\The Paper (2025)": 8102218176,
|
||||
"P:\\tv\\Below Deck": 47516712212,
|
||||
"P:\\tv\\The Penguin": 4459075060,
|
||||
"P:\\tv\\The Pretender": 18425629462,
|
||||
"P:\\tv\\The Queen's Gambit": 4100494817,
|
||||
"P:\\tv\\The Rain (2018)": 2941174698,
|
||||
"P:\\tv\\The Santa Clauses (2022)": 6400385164,
|
||||
"P:\\tv\\The Second Best Hospital in the Galaxy (2024)": 3636394169,
|
||||
"P:\\tv\\The Split": 7970767632,
|
||||
"P:\\tv\\The Studio (2025)": 11530554023,
|
||||
"P:\\tv\\The Take": 6020370013,
|
||||
"P:\\tv\\The Terminal List - Dark Wolf (2025)": 9939046560,
|
||||
"P:\\tv\\The Traitors (US) (2023)": 48149750078,
|
||||
"P:\\tv\\The Trunk (2024)": 16810949304,
|
||||
"P:\\tv\\The Umbrella Academy": 55348092191,
|
||||
"P:\\tv\\Time Bandits (2024)": 6997478287,
|
||||
"P:\\tv\\Tires (2024)": 5375794389,
|
||||
"P:\\tv\\Titans (2018)": 31986198137,
|
||||
"P:\\tv\\Tokyo Override (2024)": 3802255332,
|
||||
"P:\\tv\\Tomb Raider - The Legend of Lara Croft": 9341088252,
|
||||
"P:\\tv\\Banshee (2013)": 25030541772,
|
||||
"P:\\tv\\Dungeons & Dragons": 6660128393,
|
||||
"P:\\tv\\Made For Love (2021)": 2211136772,
|
||||
"P:\\tv\\Sirens (2025)": 4246622090,
|
||||
"P:\\tv\\Landman (2024)": 35968103808,
|
||||
"P:\\tv\\Last Man Standing": 49393251846,
|
||||
"P:\\tv\\Alien - Earth (2025)": 2926145405,
|
||||
"P:\\tv\\The Big Door Prize": 2314902686,
|
||||
"P:\\tv\\Government Cheese (2025)": 15970704500,
|
||||
"P:\\tv\\In the Dark (2019)": 2555891397,
|
||||
"P:\\tv\\Tulsa King": 41351406080,
|
||||
"P:\\tv\\Twisted Metal (2023)": 12547412897,
|
||||
"P:\\tv\\Um, Actually": 12360993522,
|
||||
"P:\\tv\\Unstable": 5444623642,
|
||||
"P:\\tv\\Utopia (AU)": 8691287022,
|
||||
"P:\\tv\\Very Important People": 12237876110,
|
||||
"P:\\tv\\Vice Principals (2016)": 18406955713,
|
||||
"P:\\tv\\Vikings (2013)": 194095449878,
|
||||
"P:\\tv\\Villainous (2017)": 1961793524,
|
||||
"P:\\tv\\Walker": 5492500161,
|
||||
"P:\\tv\\Wandavision": 10099450034,
|
||||
"P:\\tv\\Welcome to Chippendales (2022)": 10423545837,
|
||||
"P:\\tv\\Welcome to Wrexham": 66664948104,
|
||||
"P:\\tv\\What If": 21312022582,
|
||||
"P:\\tv\\Wildemount Wildlings (2025)": 3348907992,
|
||||
"P:\\tv\\Winning Time - The Rise of the Lakers Dynasty (2022)": 37911197652,
|
||||
"P:\\tv\\Dopesick": 2571994785,
|
||||
"P:\\tv\\Taylor (2025)": 2621206209,
|
||||
"P:\\tv\\Star Trek Lower Decks": 33090597113,
|
||||
"P:\\tv\\Face Off (2011)": 83155672195,
|
||||
"P:\\tv\\Catch-22": 7113496871,
|
||||
"P:\\tv\\Canada's Drag Race": 106759553819,
|
||||
"P:\\tv\\Over the Garden Wall": 2937573633,
|
||||
"P:\\tv\\The Traitors (US) (2023)": 48149750078,
|
||||
"P:\\tv\\1923": 22125507023,
|
||||
"P:\\tv\\Loki": 20082144632,
|
||||
"P:\\tv\\House of the Dragon": 23959073249,
|
||||
"P:\\tv\\The Trunk (2024)": 16810949304,
|
||||
"P:\\tv\\The Chosen (2019)": 54241850899,
|
||||
"P:\\tv\\Lucky Hank": 7336222432,
|
||||
"P:\\tv\\Station Eleven": 2708694925,
|
||||
"P:\\tv\\Kitchen Nightmares UK": 11563663098,
|
||||
"P:\\tv\\The Good Lord Bird (2020)": 5619421375,
|
||||
"P:\\tv\\Wolf Pack": 6844099384,
|
||||
"P:\\tv\\WondLa": 1399628000,
|
||||
"P:\\tv\\Worst Cooks in America (2010)": 22063867049,
|
||||
"P:\\tv\\Below Deck Mediterranean": 40713122628,
|
||||
"P:\\tv\\The Old Man (2022)": 26139845941,
|
||||
"P:\\tv\\Schitt's Creek": 9325109901,
|
||||
"P:\\tv\\Mr. & Mrs. Smith (2024)": 5316681916,
|
||||
"P:\\tv\\Percy Jackson and the Olympians": 3558450335,
|
||||
"P:\\tv\\Firefly (2002)": 7517428895,
|
||||
"P:\\tv\\Ballers": 13002096756,
|
||||
"P:\\tv\\Bupkis": 13034439710,
|
||||
"P:\\tv\\The Offer": 9070667475,
|
||||
"P:\\tv\\Life After People (2009)": 45628647899,
|
||||
"P:\\tv\\The Lord of the Rings - The Rings of Power": 12834498889,
|
||||
"P:\\tv\\Paradise (2025)": 8024209737,
|
||||
"P:\\tv\\Nobody Wants This": 11516933757,
|
||||
"P:\\tv\\Shrinking (2023)": 17293593983,
|
||||
"P:\\tv\\Hawkeye": 13524278345,
|
||||
"P:\\tv\\Home Economics": 14315967074,
|
||||
"P:\\tv\\Time Bandits (2024)": 6997478287,
|
||||
"P:\\tv\\Lessons in Chemistry (2023)": 5485801173,
|
||||
"P:\\tv\\1883": 4514294832,
|
||||
"P:\\tv\\Love, Death & Robots (2019)": 8204860116,
|
||||
"P:\\tv\\The Legend of Vox Machina": 25197294503,
|
||||
"P:\\tv\\Harry Potter - Wizards of Baking (2024)": 23545641052,
|
||||
"P:\\tv\\The Bachelor": 40368931577,
|
||||
"P:\\tv\\American Horror Story": 142468660014,
|
||||
"P:\\tv\\Yellowstone (2018)": 89724605866,
|
||||
"P:\\tv\\St. Denis Medical (2024)": 19403375683,
|
||||
"P:\\tv\\Cobra Kai": 39761471967,
|
||||
"P:\\tv\\Power (2014)": 20414619656,
|
||||
"P:\\tv\\The Originals (2013)": 72912846985,
|
||||
"P:\\tv\\The Edge of Sleep": 1358235145,
|
||||
"P:\\tv\\3 Body Problem": 11369334730,
|
||||
"P:\\tv\\New Girl": 40676856398,
|
||||
"P:\\tv\\Assembly Required (2021)": 5737519036,
|
||||
"P:\\tv\\30 Rock (2006)": 81412969909,
|
||||
"P:\\tv\\Rupauls Drag Race UK vs The World": 35504142221,
|
||||
"P:\\tv\\Daredevil - Born Again (2025)": 7647367391,
|
||||
"P:\\tv\\Brooklyn Nine Nine": 45722673163,
|
||||
"P:\\tv\\Taskmaster - Champion of Champions": 2700754514,
|
||||
"P:\\tv\\Kim's Convenience": 30475634673,
|
||||
"P:\\tv\\The Office (US)": 161867626607,
|
||||
"P:\\tv\\Stranger Things (2016)": 66712664909,
|
||||
"P:\\tv\\Rupaul's Drag Race Vegas Revue": 2532474468,
|
||||
"P:\\tv\\The Umbrella Academy": 55348092191,
|
||||
"P:\\tv\\Secret Celebrity RuPaul's Drag Race": 4211193920,
|
||||
"P:\\tv\\Andor (2022)": 25679584728,
|
||||
"P:\\tv\\The Bondsman (2025)": 3112664353,
|
||||
"P:\\tv\\Ghosts (2021)": 4574333812,
|
||||
"P:\\tv\\Interior Chinatown": 3167640001,
|
||||
"P:\\tv\\Selfie": 5013734266,
|
||||
"P:\\tv\\Supernatural": 209274293691,
|
||||
"P:\\tv\\Superman and Lois": 44881535930,
|
||||
"P:\\tv\\Black Sails (2014)": 11356486450,
|
||||
"P:\\tv\\Taskmaster (CA) (2022)": 2431664380,
|
||||
"P:\\tv\\The Last of Us": 30545352719,
|
||||
"P:\\tv\\Halo": 6961206915,
|
||||
"P:\\tv\\Home Improvement 1991": 48878774505,
|
||||
"P:\\tv\\Detroiters (2017)": 33750584701,
|
||||
"P:\\tv\\Wildemount Wildlings (2025)": 3348907992,
|
||||
"P:\\tv\\Terminator Zero": 3384699699,
|
||||
"P:\\tv\\Um, Actually": 12360993522,
|
||||
"P:\\tv\\The Rain (2018)": 2941174698,
|
||||
"P:\\tv\\Harley Quinn": 20857796821,
|
||||
"P:\\tv\\Lawmen - Bass Reeves (2023)": 5363156538,
|
||||
"P:\\tv\\Parks and Recreation": 37277190974,
|
||||
"P:\\tv\\Mythic Quest": 16965795814,
|
||||
"P:\\tv\\Invincible (2021)": 19742824176,
|
||||
"P:\\tv\\The Bear (2022)": 43665628138,
|
||||
"P:\\tv\\Jentry Chau vs. the Underworld (2024)": 1406237358,
|
||||
"P:\\tv\\Countdown (2025)": 8935252687,
|
||||
"P:\\tv\\The Great British Bake Off": 78,
|
||||
"P:\\tv\\Smartypants": 15959708127,
|
||||
"P:\\tv\\Scenes from a Marriage (US)": 12493986505,
|
||||
"P:\\tv\\The Franchise (2024)": 2981270395,
|
||||
"P:\\tv\\Chad Powers (2025)": 2474659236,
|
||||
"P:\\tv\\Doctor Who (2005)": 5820708419,
|
||||
"P:\\tv\\Bad Monkey": 7767595411,
|
||||
"P:\\tv\\Swimming with Sharks": 4426141798,
|
||||
"P:\\tv\\English Teacher": 7603165476,
|
||||
"P:\\tv\\Resident Alien (2021)": 17522605407,
|
||||
"P:\\tv\\Krypton (2018)": 10875524680,
|
||||
"P:\\tv\\Vikings (2013)": 194095449878,
|
||||
"P:\\tv\\Arcane (2021)": 19588567847,
|
||||
"P:\\tv\\Ludwig (2024)": 2670615425,
|
||||
"P:\\tv\\Canada's Drag Race vs The World": 7844155647,
|
||||
"P:\\tv\\BattleBots (2015)": 69,
|
||||
"P:\\tv\\Abbott Elementary (2021)": 24595462535,
|
||||
"P:\\tv\\Billy the Kid": 44803721006,
|
||||
"P:\\tv\\Quantum Leap 2022": 8902776416,
|
||||
"P:\\tv\\Obi-Wan Kenobi": 13867986923,
|
||||
"P:\\tv\\Matlock (2024)": 34470939613,
|
||||
"P:\\tv\\The Fall of Diddy (2025)": 2431035593,
|
||||
"P:\\tv\\Kaos": 5164057710,
|
||||
"P:\\tv\\Shifting Gears (2025)": 13556293879,
|
||||
"P:\\tv\\Saving Hope": 33116225358,
|
||||
"P:\\tv\\Gen V (2023)": 16871757804,
|
||||
"P:\\tv\\Below Deck Sailing Yacht": 12706704039,
|
||||
"P:\\tv\\Monarch Legacy of Monsters": 18371826949,
|
||||
"P:\\tv\\High Potential": 24845798484,
|
||||
"P:\\tv\\Band of Brothers (2001)": 15129362120,
|
||||
"P:\\tv\\Quantum Leap (1989)": 39284023472,
|
||||
"P:\\tv\\Harley and the Davidsons": 76,
|
||||
"P:\\tv\\Rupaul's Drag Race All Stars": 60579323023,
|
||||
"P:\\tv\\Amazing Stories (2020)": 4281304451,
|
||||
"P:\\tv\\Murder She Wrote": 12095973826,
|
||||
"P:\\tv\\Kitchen Nightmares US": 56092851597,
|
||||
"P:\\tv\\Game Changer": 38317757866,
|
||||
"P:\\tv\\Taskmaster AU": 20527610746,
|
||||
"P:\\tv\\Fallout": 20302317773,
|
||||
"P:\\tv\\Young Sheldon": 21714069112,
|
||||
"P:\\tv\\Your Honor (2020)": 25879839349
|
||||
"P:\\tv\\Vice Principals (2016)": 18406955713,
|
||||
"P:\\tv\\Adventuring Academy": 62196997373,
|
||||
"P:\\tv\\Solar Opposites": 1138214210,
|
||||
"P:\\tv\\Pok\u00e9mon Concierge (2023)": 1134616527,
|
||||
"P:\\tv\\Better Call Saul": 31152560439,
|
||||
"P:\\tv\\Counterpart": 4875616955,
|
||||
"P:\\tv\\The Paper (2025)": 8102218176,
|
||||
"P:\\tv\\Chuck": 32193192829,
|
||||
"P:\\tv\\The Bachelorette": 9927266246,
|
||||
"P:\\tv\\Wandavision": 10099450034,
|
||||
"P:\\tv\\Pantheon": 13397374449,
|
||||
"P:\\tv\\The Gilded Age": 90505242840,
|
||||
"P:\\tv\\Gastronauts": 9365810750,
|
||||
"P:\\tv\\American Gods (2017)": 43921706762,
|
||||
"P:\\tv\\The IT Crowd (2006)": 9239572772,
|
||||
"P:\\tv\\Winning Time - The Rise of the Lakers Dynasty (2022)": 37911197652,
|
||||
"P:\\tv\\Monet's Slumber Party": 8253206091,
|
||||
"P:\\tv\\Walker": 5492500161,
|
||||
"P:\\tv\\Stargirl": 9507100884,
|
||||
"P:\\tv\\House of Guinness (2025)": 5444928896,
|
||||
"P:\\tv\\Father Brown": 18896564477,
|
||||
"P:\\tv\\Silo (2023)": 12897630564,
|
||||
"P:\\tv\\Your Honor (2020)": 25879839349,
|
||||
"P:\\tv\\Welcome to Wrexham": 66664948104,
|
||||
"P:\\tv\\Royal Pains (2009)": 1247586112,
|
||||
"P:\\tv\\The Continental (2023)": 1920206807,
|
||||
"P:\\tv\\Citadel": 2339699246,
|
||||
"P:\\tv\\The 10th Kingdom (2000)": 14174589505,
|
||||
"P:\\tv\\Parlor Room": 12022280605,
|
||||
"P:\\tv\\Its Always Sunny in Philadelphia": 84650830434,
|
||||
"P:\\tv\\Star Wars - Skeleton Crew (2024)": 2940779001,
|
||||
"P:\\tv\\Rupaul's Drag Race": 59664530796,
|
||||
"P:\\tv\\Only Murders in the Building (2021)": 2379838148,
|
||||
"P:\\tv\\Running Man": 10279755878,
|
||||
"P:\\tv\\Shetland": 18537045340,
|
||||
"P:\\tv\\Adults (2025)": 6845585714,
|
||||
"P:\\tv\\iCarly (2021)": 19966043984,
|
||||
"P:\\tv\\Villainous (2017)": 1961793524,
|
||||
"P:\\tv\\The Terminal List - Dark Wolf (2025)": 9939046560,
|
||||
"P:\\tv\\Ted Lasso (2020)": 52046307136,
|
||||
"P:\\tv\\Murderbot (2025)": 18338040970,
|
||||
"P:\\tv\\RuPaul's Drag Race Down Under": 27454793482,
|
||||
"P:\\tv\\Gravity Falls": 31900305156,
|
||||
"P:\\tv\\The Santa Clauses (2022)": 6400385164,
|
||||
"P:\\tv\\Marvel's The Punisher (2017)": 32242478897,
|
||||
"P:\\tv\\Dracula (2020)": 2147285239,
|
||||
"P:\\tv\\Extraordinary": 6934203888,
|
||||
"P:\\tv\\Cyberpunk - Edgerunners (2022)": 11313875182,
|
||||
"P:\\tv\\Rick and Morty": 31672318625,
|
||||
"P:\\tv\\Welcome to Chippendales (2022)": 10423545837,
|
||||
"P:\\tv\\Squid Game (2021)": 22082475135,
|
||||
"P:\\tv\\MobLand (2025)": 6622179548,
|
||||
"P:\\tv\\Taskmaster (NZ)": 71323320898,
|
||||
"P:\\tv\\The Newsroom": 27756667258,
|
||||
"P:\\tv\\The Pretender": 18425629462,
|
||||
"P:\\tv\\Hazbin Hotel (2024)": 10906489515,
|
||||
"P:\\tv\\Raised by wolves": 9720677524,
|
||||
"P:\\tv\\Tomb Raider - The Legend of Lara Croft": 9341088252,
|
||||
"P:\\tv\\Spartacus": 75639017886,
|
||||
"P:\\tv\\Worst Cooks in America (2010)": 27887501056,
|
||||
"P:\\tv\\Avenue 5": 12572813494,
|
||||
"P:\\tv\\Man Down (2013)": 5077144151,
|
||||
"P:\\tv\\Outlander": 27364180668,
|
||||
"P:\\tv\\The Eternaut": 17178505929,
|
||||
"P:\\tv\\Below Deck Down Under (2022)": 36006759742,
|
||||
"P:\\tv\\Dirty Laundry": 27626331672,
|
||||
"P:\\tv\\Chilling Adventures of Sabrina (2018)": 23147355371,
|
||||
"P:\\tv\\The Studio (2025)": 11530554023,
|
||||
"P:\\tv\\The Forsytes (2025)": 4034792830,
|
||||
"P:\\tv\\Platonic (2023)": 17488146510,
|
||||
"P:\\tv\\Love Island (US) (2019)": 20699120877,
|
||||
"P:\\tv\\Dark Side of the Ring": 11863132534,
|
||||
"P:\\tv\\The Day of the Jackal (2024)": 17787097381,
|
||||
"P:\\tv\\Utopia (AU)": 8691287022,
|
||||
"P:\\tv\\Sweetpea": 2706241673,
|
||||
"P:\\tv\\Dateline NBC (1992)": 19267231607,
|
||||
"P:\\tv\\Euphoria": 40925172559,
|
||||
"P:\\tv\\The Consultant (2023)": 74,
|
||||
"P:\\tv\\Titans (2018)": 31986198137,
|
||||
"P:\\tv\\Taskmaster": 142193364333,
|
||||
"P:\\tv\\Ink Master": 23329086486,
|
||||
"P:\\tv\\Dimension 20": 559394428110,
|
||||
"P:\\tv\\Continuum (2012)": 29352883496,
|
||||
"P:\\tv\\South Park": 70261225261,
|
||||
"P:\\tv\\Letterkenny": 63,
|
||||
"P:\\tv\\Ghosts (2019)": 40703143881,
|
||||
"P:\\tv\\Moon Knight": 10976093361,
|
||||
"P:\\tv\\Twisted Metal (2023)": 12547412897,
|
||||
"P:\\tv\\Extrapolations": 6690715385,
|
||||
"P:\\tv\\Quiet On Set - The Dark Side Of Kids TV": 12191520028,
|
||||
"P:\\tv\\Sh\u014dgun": 20899988683,
|
||||
"P:\\tv\\Taboo (2017)": 19309841226,
|
||||
"P:\\tv\\Ironheart (2025)": 3153557870,
|
||||
"P:\\tv\\DOTA - Dragon's Blood (2021)": 12538510766,
|
||||
"P:\\tv\\Knuckles": 2140786440,
|
||||
"P:\\tv\\Shoresy": 10192565178,
|
||||
"P:\\tv\\Impractical Jokers": 13357380400,
|
||||
"P:\\tv\\One More Time (2024)": 6434473461,
|
||||
"P:\\tv\\Crowd Control": 9644641207,
|
||||
"P:\\tv\\Dimension 20's Adventuring Party": 12002285238,
|
||||
"P:\\tv\\Special Ops Lioness": 9765393961,
|
||||
"P:\\tv\\Ted (2024)": 3024624414,
|
||||
"P:\\tv\\Mighty Nein (2025)": 6138965943,
|
||||
"P:\\tv\\Citadel - Diana": 13304679453,
|
||||
"P:\\tv\\Our Flag Means Death": 2107045664,
|
||||
"P:\\tv\\Make Some Noise": 26524123873,
|
||||
"P:\\tv\\Mayor of Kingstown (2021)": 65464041666,
|
||||
"P:\\tv\\The Take": 6020370013,
|
||||
"P:\\tv\\Agatha All Along": 3411637969,
|
||||
"P:\\tv\\The Amazing Digital Circus (2023)": 4739070191,
|
||||
"P:\\tv\\The Now": 836886747,
|
||||
"P:\\tv\\Poppa\u2019s House": 13794748297,
|
||||
"P:\\tv\\Married at First Sight (2014)": 30275711911,
|
||||
"P:\\tv\\The Closer": 47449608535,
|
||||
"P:\\tv\\Junior Taskmaster (2024)": 4133620030,
|
||||
"P:\\tv\\WondLa": 1399628000,
|
||||
"P:\\tv\\The Second Best Hospital in the Galaxy (2024)": 3636394169,
|
||||
"P:\\tv\\Being Human (2011)": 66311454464,
|
||||
"P:\\tv\\SCORPION": 54081802764,
|
||||
"P:\\tv\\The Goes Wrong Show (2019)": 3676343887,
|
||||
"P:\\tv\\See": 12316511887,
|
||||
"P:\\tv\\Dirk Gently's Holistic Detective Agency (2016)": 11935610182,
|
||||
"P:\\tv\\Tokyo Override (2024)": 3802255332,
|
||||
"P:\\tv\\Peacemaker (2022)": 13199970800,
|
||||
"P:\\tv\\The Falcon and The Winter Soldier (2021)": 11657055937,
|
||||
"P:\\tv\\Fargo (2014)": 93247402537,
|
||||
"P:\\tv\\Killer Cakes": 3673781461,
|
||||
"P:\\tv\\The Mandalorian": 36487773789,
|
||||
"P:\\tv\\Very Important People": 14563355278,
|
||||
"P:\\tv\\Smiling Friends": 5633340834,
|
||||
"P:\\tv\\Game Changers (2024)": 5880504271,
|
||||
"P:\\tv\\Star Strek Strange New Worlds": 13781151928,
|
||||
"P:\\tv\\Galavant": 12147863291,
|
||||
"P:\\tv\\She-Hulk Attorney at Law": 10233633417,
|
||||
"P:\\tv\\From Dusk Till Dawn - The Series (2014)": 5360771338,
|
||||
"P:\\tv\\The Journal of the Mysterious Creatures (2019)": 92,
|
||||
"P:\\tv\\Fallen (2024)": 4161867429,
|
||||
"P:\\tv\\Severance": 15044806873,
|
||||
"P:\\tv\\The Great (2020)": 22361386693,
|
||||
"P:\\tv\\What If": 21312022582,
|
||||
"P:\\tv\\Rupaul's Drag Race UK": 110914388896,
|
||||
"P:\\tv\\Game Of Thrones": 119681469870,
|
||||
"P:\\tv\\Belgravia - The Next Chapter": 8340040939,
|
||||
"P:\\tv\\Hitmen (2020)": 12274410846,
|
||||
"P:\\tv\\Haunted Hotel (2025)": 4735071992,
|
||||
"P:\\tv\\The Book of Boba Fett": 12039417291,
|
||||
"P:\\tv\\SAS Rogue Heroes (2022)": 10733559643,
|
||||
"P:\\tv\\Dwight in Shining Armor": 75,
|
||||
"P:\\tv\\Jury Duty": 8010062372,
|
||||
"P:\\tv\\Son of Zorn (2016)": 6780978712,
|
||||
"P:\\tv\\The Gentlemen (2024)": 5224500371,
|
||||
"P:\\tv\\Schmigadoon!": 6206632733,
|
||||
"P:\\tv\\The Drew Carey Show (1995)": 70,
|
||||
"P:\\tv\\Fired on Mars (2023)": 3590992124,
|
||||
"P:\\tv\\Black Bird (2022)": 5893929480,
|
||||
"P:\\tv\\Billions": 31141419259,
|
||||
"P:\\tv\\Reacher (2022)": 17521873037,
|
||||
"P:\\tv\\The Morning Show": 94311701751,
|
||||
"P:\\tv\\Secret Level": 2810124465,
|
||||
"P:\\tv\\The Boys": 68010010167,
|
||||
"P:\\tv\\Gordon Ramsay's Food Stars (2023)": 6344621632,
|
||||
"P:\\tv\\Death and Other Details": 17844763765,
|
||||
"P:\\tv\\Modern Family": 82788065200,
|
||||
"P:\\tv\\Married... with Children (1987)": 64228823786,
|
||||
"P:\\tv\\BattleBots": 61,
|
||||
"P:\\tv\\Silicon Valley (2014)": 63657428121,
|
||||
"P:\\tv\\Tires (2024)": 5375794389,
|
||||
"P:\\tv\\Creature Commandos (2024)": 2331424358,
|
||||
"P:\\tv\\Goosebumps (2023)": 8257419062,
|
||||
"P:\\tv\\The Fall of the House of Usher (2023)": 16454192941,
|
||||
"P:\\tv\\Passion for punchlines": 75514795,
|
||||
"P:\\tv\\The Queen's Gambit": 4100494817,
|
||||
"P:\\tv\\Suits LA (2025)": 22274831381,
|
||||
"P:\\tv\\Dune - Prophecy": 3330003290,
|
||||
"P:\\tv\\Unstable": 5444623642,
|
||||
"P:\\tv\\The Split": 7970767632,
|
||||
"P:\\tv\\Barry": 31934844666,
|
||||
"P:\\tv\\The Dragon Dentist": 11317084093,
|
||||
"P:\\tv\\Kevin Can F-k Himself": 11614889793
|
||||
}
|
||||
422
ARCHITECTURE.md
Normal file
422
ARCHITECTURE.md
Normal file
@ -0,0 +1,422 @@
|
||||
# Interactive Audio Stream Selection - Architecture Diagram
|
||||
|
||||
## System Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ main.py │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ ArgumentParser │ │
|
||||
│ │ --filter-audio (enables audio filtering) │ │
|
||||
│ │ --interactive (enables interactive mode) ← NEW │ │
|
||||
│ │ --cq, --r, --m, --language, --test (existing) │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ normalize_input_path() → folder path │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ process_folder( │ │
|
||||
│ │ filter_audio=True/False, │ │
|
||||
│ │ interactive_audio=True/False ← NEW │ │
|
||||
│ │ ) │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ core/process_manager.py │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ process_folder(folder, ..., filter_audio, interactive) │ │
|
||||
│ │ ↑ NEW param │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ For each video file: │ │
|
||||
│ │ 1. Get source resolution & target resolution │ │
|
||||
│ │ 2. Create audio_filter_config dict: │ │
|
||||
│ │ { │ │
|
||||
│ │ "enabled": filter_audio, │ │
|
||||
│ │ "interactive": interactive_audio ← NEW FIELD │ │
|
||||
│ │ } │ │
|
||||
│ │ 3. Call run_ffmpeg() with audio_filter_config │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ core/encode_engine.py │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ run_ffmpeg( │ │
|
||||
│ │ input_file, output_file, ..., │ │
|
||||
│ │ audio_filter_config={enabled, interactive} │ │
|
||||
│ │ ) │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ 1. streams = get_audio_streams(input_file) │ │
|
||||
│ │ └─ Returns: [(index, ch, br, lang, meta), ...] │ │
|
||||
│ │ │ │
|
||||
│ │ 2. if audio_filter_config.get("enabled"): │ │
|
||||
│ │ ├─ if audio_filter_config.get("interactive"): │ │
|
||||
│ │ │ └─ Call: prompt_user_audio_selection(streams) ← ◆ │ │
|
||||
│ │ │ [SHOWS PROMPT TO USER] │ │
|
||||
│ │ │ └─ Returns: filtered_streams │ │
|
||||
│ │ │ │ │
|
||||
│ │ └─ else: │ │
|
||||
│ │ └─ Call: filter_audio_streams(input_file, streams) │ │
|
||||
│ │ (Automatic: keep best English + Commentary) │ │
|
||||
│ │ └─ Returns: filtered_streams │ │
|
||||
│ │ │ │
|
||||
│ │ 3. For each stream in filtered_streams: │ │
|
||||
│ │ ├─ choose_audio_bitrate() (codec selection) │ │
|
||||
│ │ └─ Build FFmpeg codec params (-c:a, -b:a, etc.) │ │
|
||||
│ │ │ │
|
||||
│ │ 4. subprocess.run(ffmpeg_cmd) │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ core/audio_handler.py │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ def prompt_user_audio_selection(streams) ← NEW FUNCTION │ │
|
||||
│ │ ◆ Interactive User Prompt ◆ │ │
|
||||
│ │ │ │
|
||||
│ │ Display: │ │
|
||||
│ │ ┌──────────────────────────────────────────────┐ │ │
|
||||
│ │ │ 🎵 AUDIO STREAM SELECTION │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ Stream #0: 2ch | Lang: eng | Bitrate: 128kbps │ │
|
||||
│ │ │ Stream #1: 6ch | Lang: eng | Bitrate: 448kbps │ │
|
||||
│ │ │ Stream #2: 2ch | Lang: spa | Bitrate: 128kbps │ │
|
||||
│ │ │ Stream #3: 2ch | Lang: comment | Bitrate: 64kbps │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ Keep streams: 1,3 │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ ✅ Keeping 2 stream(s), removing 2 stream(s) │ │
|
||||
│ │ └──────────────────────────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ Process: │ │
|
||||
│ │ 1. Check if streams empty/single → return as-is │ │
|
||||
│ │ 2. Display all streams with formatting │ │
|
||||
│ │ 3. Prompt user for comma-separated indices │ │
|
||||
│ │ 4. Parse and validate input │ │
|
||||
│ │ 5. Filter streams to selected only │ │
|
||||
│ │ 6. Log selections & removed streams │ │
|
||||
│ │ 7. Return filtered_streams │ │
|
||||
│ │ │ │
|
||||
│ │ Error Handling: │ │
|
||||
│ │ • Invalid input → Keep all (log warning) │ │
|
||||
│ │ • No selections → Keep all (log warning) │ │
|
||||
│ │ • Empty input → Keep all (user confirmed) │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Data Flow Example
|
||||
|
||||
### User Command
|
||||
```bash
|
||||
python main.py "C:\Videos" --filter-audio --interactive
|
||||
```
|
||||
|
||||
### Data Transformation
|
||||
|
||||
```
|
||||
Step 1: ArgumentParser
|
||||
─────────────────────
|
||||
Input Args:
|
||||
folder = "C:\Videos"
|
||||
filter_audio = True
|
||||
interactive_audio = True
|
||||
|
||||
Output: args object
|
||||
|
||||
────────────────────────────────────────────────────────
|
||||
|
||||
Step 2: main() → process_folder()
|
||||
───────────────────────────────────
|
||||
Input:
|
||||
folder, filter_audio=True, interactive_audio=True
|
||||
|
||||
Output: Called with both flags
|
||||
|
||||
────────────────────────────────────────────────────────
|
||||
|
||||
Step 3: process_folder() → Builds audio_filter_config
|
||||
──────────────────────────────────────────────────────
|
||||
Input:
|
||||
filter_audio=True
|
||||
interactive_audio=True
|
||||
|
||||
Logic:
|
||||
if filter_audio is not None:
|
||||
audio_filter_config = {
|
||||
"enabled": True,
|
||||
"interactive": True ← NEW
|
||||
}
|
||||
|
||||
Output: audio_filter_config dict
|
||||
|
||||
────────────────────────────────────────────────────────
|
||||
|
||||
Step 4: process_folder() → run_ffmpeg()
|
||||
─────────────────────────────────────────
|
||||
Input:
|
||||
input_file = "movie.mkv"
|
||||
audio_filter_config = {"enabled": True, "interactive": True}
|
||||
|
||||
Output: Called with config
|
||||
|
||||
────────────────────────────────────────────────────────
|
||||
|
||||
Step 5: run_ffmpeg() → Audio Stream Detection
|
||||
──────────────────────────────────────────────
|
||||
Input:
|
||||
input_file = "movie.mkv"
|
||||
|
||||
Output:
|
||||
streams = [
|
||||
(0, 2, 128, "eng", 0), # Stream #0: 2ch English 128kbps
|
||||
(1, 6, 448, "eng", 0), # Stream #1: 6ch English 448kbps
|
||||
(2, 2, 128, "spa", 0), # Stream #2: 2ch Spanish 128kbps
|
||||
(3, 2, 64, "und", 0) # Stream #3: 2ch Undefined 64kbps
|
||||
]
|
||||
|
||||
────────────────────────────────────────────────────────
|
||||
|
||||
Step 6: Audio Filtering Decision
|
||||
────────────────────────────────
|
||||
Input:
|
||||
audio_filter_config = {"enabled": True, "interactive": True}
|
||||
streams = [4 streams above]
|
||||
|
||||
Logic:
|
||||
if audio_filter_config.get("enabled"): ✓ True
|
||||
if audio_filter_config.get("interactive"): ✓ True
|
||||
→ Call prompt_user_audio_selection() ← INTERACTIVE PATH
|
||||
|
||||
Output: User prompt shown to console
|
||||
|
||||
────────────────────────────────────────────────────────
|
||||
|
||||
Step 7: prompt_user_audio_selection() → User Input
|
||||
──────────────────────────────────────────────────────
|
||||
Input:
|
||||
streams = [4 streams]
|
||||
|
||||
Display:
|
||||
🎵 AUDIO STREAM SELECTION
|
||||
════════════════════════════════════════════════════
|
||||
Stream #0: 2ch | Lang: eng | Bitrate: 128kbps
|
||||
Stream #1: 6ch | Lang: eng | Bitrate: 448kbps
|
||||
Stream #2: 2ch | Lang: spa | Bitrate: 128kbps
|
||||
Stream #3: 2ch | Lang: undefined | Bitrate: 64kbps
|
||||
|
||||
Keep streams: ← WAIT FOR USER INPUT
|
||||
|
||||
User Input:
|
||||
"1,3"
|
||||
|
||||
Parse:
|
||||
selected_indices = {1, 3}
|
||||
|
||||
Filter:
|
||||
filtered = [
|
||||
(1, 6, 448, "eng", 0), ✓ Keep
|
||||
(3, 2, 64, "und", 0) ✓ Keep
|
||||
]
|
||||
|
||||
Output:
|
||||
✅ Keeping 2 stream(s), removing 2 stream(s)
|
||||
|
||||
Return: filtered streams
|
||||
|
||||
────────────────────────────────────────────────────────
|
||||
|
||||
Step 8: Back to run_ffmpeg() → Codec Selection
|
||||
──────────────────────────────────────────────
|
||||
Input:
|
||||
streams = [
|
||||
(1, 6, 448, "eng", 0),
|
||||
(3, 2, 64, "und", 0)
|
||||
]
|
||||
|
||||
Process each stream:
|
||||
Stream 1: 6ch → choose_audio_bitrate() → ("eac3", 384000)
|
||||
Stream 3: 2ch → choose_audio_bitrate() → ("aac", 160000)
|
||||
|
||||
Output:
|
||||
FFmpeg codec params:
|
||||
-c:a:1 eac3 -b:a:1 384k -ac:1 6 -channel_layout:1 5.1
|
||||
-c:a:3 aac -b:a:3 160k -ac:3 2 -channel_layout:3 stereo
|
||||
|
||||
────────────────────────────────────────────────────────
|
||||
|
||||
Step 9: FFmpeg Encoding
|
||||
───────────────────────
|
||||
Input:
|
||||
ffmpeg -i movie.mkv \
|
||||
-vf scale=... \
|
||||
-c:v av1_nvenc \
|
||||
-c:a:1 eac3 -b:a:1 384k ... \
|
||||
-c:a:3 aac -b:a:3 160k ... \
|
||||
output.mkv
|
||||
|
||||
Process:
|
||||
FFmpeg encodes video and audio streams
|
||||
Only streams 1 and 3 included (streams 0 and 2 excluded)
|
||||
|
||||
Output:
|
||||
output.mkv (with only selected audio tracks)
|
||||
```
|
||||
|
||||
## State Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ User Runs Script │
|
||||
│ --filter-audio --interactive │
|
||||
└──────────────┬──────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Parse Arguments │
|
||||
│ interactive_audio = True │
|
||||
└──────────────┬──────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ process_folder() │
|
||||
│ Build audio_filter_config │
|
||||
│ {enabled: T, interactive: T} │
|
||||
└──────────────┬──────────────────┘
|
||||
│
|
||||
┌────────────┴────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
For each file Detect audio streams
|
||||
┌──────────────┐ get_audio_streams()
|
||||
│ run_ffmpeg() │ └─ Returns 4 streams
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────┐
|
||||
│ Check filter enabled? │
|
||||
│ audio_filter_config │
|
||||
└──────┬─────────────┬─────┘
|
||||
│ No │ Yes
|
||||
│ ▼
|
||||
│ ┌─────────────────────┐
|
||||
│ │ Check interactive? │
|
||||
│ └────┬────────────┬───┘
|
||||
│ │ No │ Yes
|
||||
│ │ ▼
|
||||
│ │ ┌───────────────────┐
|
||||
│ │ │ INTERACTIVE PROMPT│
|
||||
│ │ │ Show streams │
|
||||
│ │ │ Get user input │
|
||||
│ │ │ Filter streams │
|
||||
│ │ └─────────┬─────────┘
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────┐ │
|
||||
│ │ Automatic Filter │ │
|
||||
│ │ (Best English + │ │
|
||||
│ │ Commentary) │ │
|
||||
│ └─────────┬────────┘ │
|
||||
│ │ │
|
||||
└────────────────┴───────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────┐
|
||||
│ Apply Codec Selection │
|
||||
│ (for selected streams only) │
|
||||
│ choose_audio_bitrate() │
|
||||
└────────────┬───────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────┐
|
||||
│ Build FFmpeg Command │
|
||||
│ (with selected audio streams) │
|
||||
└────────────┬───────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────┐
|
||||
│ Run FFmpeg Encoding │
|
||||
│ subprocess.run(cmd) │
|
||||
└────────────┬───────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────┐
|
||||
│ Success/Failure Handling │
|
||||
│ Log Results │
|
||||
└────────────┬───────────────────┘
|
||||
│
|
||||
┌────────────┴─────────┐
|
||||
│ │
|
||||
Next file? Process Complete
|
||||
```
|
||||
|
||||
## Component Interaction
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ main.py │
|
||||
└──────┬──────┘
|
||||
│ calls with (filter_audio, interactive_audio)
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ process_manager.py │
|
||||
├──────────────────────┤
|
||||
│ • Build config │ ◄─── Set "interactive" field
|
||||
│ • For each file: │ in audio_filter_config
|
||||
│ └─ run_ffmpeg() │
|
||||
└──────┬───────────────┘
|
||||
│ passes audio_filter_config
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ encode_engine.py │
|
||||
├──────────────────────┤
|
||||
│ • Check "enabled" │ ◄─── Decide which
|
||||
│ • Check "interactive"│ filtering method
|
||||
│ • Route to: │ to use
|
||||
│ ├─ interactive path│
|
||||
│ └─ automatic path │
|
||||
└──────┬───────────────┘
|
||||
│ passes streams
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ audio_handler.py │
|
||||
├──────────────────────┤
|
||||
│ • Interactive: │
|
||||
│ prompt_user_...() │◄──── NEW FUNCTION
|
||||
│ └─ Show & filter │ Shows prompt
|
||||
│ │ Gets user input
|
||||
│ • Automatic: │ Returns filtered
|
||||
│ filter_audio_...() │
|
||||
│ └─ Logic filter │
|
||||
└──────────────────────┘
|
||||
│ returns filtered streams
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ encode_engine.py │
|
||||
├──────────────────────┤
|
||||
│ • Codec selection │
|
||||
│ • Build FFmpeg cmd │
|
||||
│ • Run encoding │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This architecture ensures clean separation of concerns:
|
||||
- **main.py**: CLI interface
|
||||
- **process_manager.py**: Orchestration & config building
|
||||
- **encode_engine.py**: FFmpeg command building & execution
|
||||
- **audio_handler.py**: Audio detection & stream filtering
|
||||
|
||||
The interactive prompt is cleanly isolated in `audio_handler.py` and only called when needed.
|
||||
81
ENCODER_SWITCH.md
Normal file
81
ENCODER_SWITCH.md
Normal file
@ -0,0 +1,81 @@
|
||||
# Dual Encoder Support - Implementation Complete ✅
|
||||
|
||||
## Features Added
|
||||
|
||||
The transcoder now supports switching between two video encoders via the `--encoder` CLI option:
|
||||
|
||||
### 1. **HEVC NVENC 10-bit** (Default)
|
||||
- **Command**: `--encoder nvenc` or default (no flag needed)
|
||||
- **Codec**: `hevc_nvenc`
|
||||
- **Preset**: `slow` (high quality)
|
||||
- **Bit Depth**: 10-bit
|
||||
- **Pixel Format**: `yuv420p10le`
|
||||
- **Use Case**: Best quality archival format, suitable for Plex compatibility
|
||||
|
||||
### 2. **AV1 NVENC 8-bit**
|
||||
- **Command**: `--encoder av1`
|
||||
- **Codec**: `av1_nvenc`
|
||||
- **Preset**: `p7` (high quality)
|
||||
- **Bit Depth**: 8-bit
|
||||
- **Pixel Format**: `yuv420p`
|
||||
- **Use Case**: Maximum file size reduction, modern playback devices
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```bash
|
||||
# Default to HEVC NVENC 10-bit with smart resolution scaling
|
||||
python main.py "C:\Videos\Movies"
|
||||
|
||||
# Force AV1 NVENC 8-bit encoding
|
||||
python main.py "C:\Videos\TV" --encoder av1
|
||||
|
||||
# AV1 with explicit resolution
|
||||
python main.py "C:\Videos\Anime" --encoder av1 --r 1080
|
||||
|
||||
# AV1 with CQ mode at specific quality
|
||||
python main.py "C:\Videos\Low-Res" --encoder av1 --cq 28
|
||||
|
||||
# AV1 with bitrate mode
|
||||
python main.py "C:\Videos\Movies" --encoder av1 --m bitrate
|
||||
|
||||
# HEVC (explicit, though it's the default)
|
||||
python main.py "C:\Videos\TV" --encoder nvenc --cq 26
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Encoder settings are stored in `config.xml`:
|
||||
|
||||
```xml
|
||||
<encoder default="nvenc">
|
||||
<av1_nvenc preset="p7" bit_depth="8" pix_fmt="yuv420p" />
|
||||
<hevc_nvenc preset="slow" bit_depth="10" pix_fmt="yuv420p10le" />
|
||||
</encoder>
|
||||
```
|
||||
|
||||
The `default="nvenc"` attribute can be changed, but CLI `--encoder` flag always takes precedence.
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. **config.xml** - Added `<encoder>` section with both encoder configurations
|
||||
2. **main.py** - Added `--encoder` argument, defaults to "nvenc"
|
||||
3. **encode_engine.py** - Updated `run_ffmpeg()` to:
|
||||
- Accept `encoder` parameter
|
||||
- Dynamically set encoder codec, preset, bit depth, and pixel format
|
||||
- Display encoder details in logging output
|
||||
4. **process_manager.py** - Updated to:
|
||||
- Accept and pass `encoder` parameter through processing pipeline
|
||||
- Updated both Phase 1 (initial encode) and Phase 2 (bitrate retry) encode calls
|
||||
|
||||
## Quality Notes
|
||||
|
||||
| Aspect | HEVC NVENC | AV1 NVENC |
|
||||
|--------|-----------|----------|
|
||||
| **File Size** | ~80-90% of AV1 | Smallest (baseline) |
|
||||
| **Quality** | Excellent | Excellent |
|
||||
| **Preset** | slow (p6) | p7 |
|
||||
| **Bit Depth** | 10-bit | 8-bit |
|
||||
| **Compatibility** | Excellent (Plex) | Good (modern devices) |
|
||||
| **Encoding Speed** | Fast | Fast |
|
||||
|
||||
Both encoders use NVIDIA GPU acceleration (NVENC) for fast encoding.
|
||||
280
IMPLEMENTATION_COMPLETE.md
Normal file
280
IMPLEMENTATION_COMPLETE.md
Normal file
@ -0,0 +1,280 @@
|
||||
# Interactive Audio Stream Selection - Complete Implementation
|
||||
|
||||
## Overview
|
||||
✅ **COMPLETE** - Interactive audio stream selection feature has been successfully implemented.
|
||||
|
||||
Users can now view all available audio streams in each video file and select which ones to keep for encoding, providing fine-grained control over audio track inclusion.
|
||||
|
||||
## Features Implemented
|
||||
|
||||
### 1. Stream Display ✅
|
||||
- Shows all audio streams with human-readable format
|
||||
- Displays: Stream number, channel count, language code, bitrate
|
||||
- Clear visual separation and organized layout
|
||||
- Example: `Stream #0: 2ch | Lang: eng | Bitrate: 128kbps`
|
||||
|
||||
### 2. User Input ✅
|
||||
- Accepts comma-separated stream indices: `0,1,3`
|
||||
- Accepts single stream: `1`
|
||||
- Accepts blank input (keep all streams)
|
||||
- Input validation with helpful error messages
|
||||
- Optional spaces in comma-separated list: `0, 1, 3`
|
||||
|
||||
### 3. Filtering ✅
|
||||
- Removes non-selected streams from encoding
|
||||
- Preserves original stream indices for FFmpeg mapping
|
||||
- Logs all selections and removals
|
||||
- Falls back to keeping all streams on invalid input
|
||||
|
||||
### 4. CLI Integration ✅
|
||||
- New flag: `--interactive` (boolean)
|
||||
- Works with `--filter-audio` flag
|
||||
- Can be used independently (auto-enables filtering)
|
||||
- Integrated into argument parser with help text
|
||||
|
||||
### 5. Processing Pipeline ✅
|
||||
- Called from `run_ffmpeg()` in encode_engine.py
|
||||
- Executed after stream detection
|
||||
- Executed before codec selection
|
||||
- Per-file prompting (allows different selections per video)
|
||||
|
||||
### 6. Logging ✅
|
||||
- Logs user selections: `User selected X audio stream(s): [0, 1, 3]`
|
||||
- Logs removed streams: `Removed X audio stream(s): [2]`
|
||||
- Logs invalid input attempts
|
||||
- Integrated with project's logging system
|
||||
|
||||
## File Changes Summary
|
||||
|
||||
### main.py
|
||||
**Added**:
|
||||
- `--interactive` argument to argparse
|
||||
- Pass `args.interactive_audio` to `process_folder()`
|
||||
|
||||
**Lines Changed**: 2
|
||||
|
||||
### core/process_manager.py
|
||||
**Added**:
|
||||
- `interactive_audio: bool = False` parameter to function signature
|
||||
- Logic to set `audio_filter_config["interactive"]` based on CLI args
|
||||
- Auto-enable filtering if `--interactive` used without `--filter-audio`
|
||||
|
||||
**Lines Changed**: ~5
|
||||
|
||||
### core/encode_engine.py
|
||||
**Added**:
|
||||
- Import `prompt_user_audio_selection`
|
||||
- Check for `audio_filter_config.get("interactive", False)`
|
||||
- Route to interactive or automatic filtering accordingly
|
||||
|
||||
**Lines Changed**: ~5
|
||||
|
||||
### core/audio_handler.py
|
||||
**Added**:
|
||||
- `prompt_user_audio_selection()` function (64 lines)
|
||||
- Comprehensive docstring
|
||||
- User-friendly output formatting
|
||||
- Input validation and error handling
|
||||
- Logging integration
|
||||
|
||||
**Lines Changed**: +64 (new function)
|
||||
|
||||
## Code Structure
|
||||
|
||||
### Function: `prompt_user_audio_selection(streams: list) -> list`
|
||||
**Location**: `core/audio_handler.py` (line 297)
|
||||
|
||||
**Parameters**:
|
||||
- `streams`: List of (index, channels, bitrate, language, metadata) tuples
|
||||
|
||||
**Returns**:
|
||||
- Filtered list containing only user-selected streams
|
||||
|
||||
**Key Features**:
|
||||
1. Early return if 0-1 streams (no selection needed)
|
||||
2. Display header with visual formatting
|
||||
3. Show each stream with index, channels, language, bitrate
|
||||
4. Prompt for user input with examples
|
||||
5. Parse comma-separated input
|
||||
6. Validate stream indices
|
||||
7. Handle edge cases (empty input, invalid input)
|
||||
8. Log results to project logger
|
||||
9. Return filtered streams ready for encoding
|
||||
|
||||
**Error Handling**:
|
||||
- ValueError on unparseable input → keep all
|
||||
- No valid selections → keep all with warning
|
||||
- Empty input → keep all (user confirmed)
|
||||
|
||||
## Execution Flow
|
||||
|
||||
```
|
||||
User runs:
|
||||
$ python main.py "C:\Videos" --filter-audio --interactive
|
||||
|
||||
↓
|
||||
|
||||
main.py parses arguments
|
||||
- filter_audio = True (from --filter-audio)
|
||||
- interactive_audio = True (from --interactive)
|
||||
|
||||
↓
|
||||
|
||||
process_folder() called with both flags
|
||||
|
||||
↓
|
||||
|
||||
For each video file:
|
||||
└─ run_ffmpeg() called
|
||||
└─ get_audio_streams() detects streams
|
||||
└─ Check audio_filter_config.enabled
|
||||
└─ True: Apply filtering
|
||||
└─ Check audio_filter_config.interactive
|
||||
└─ True: Call prompt_user_audio_selection()
|
||||
└─ [INTERACTIVE PROMPT APPEARS]
|
||||
└─ User sees streams and selects
|
||||
└─ Returns filtered stream list
|
||||
└─ False: Call filter_audio_streams()
|
||||
└─ Automatic filtering (keep best English + Commentary)
|
||||
└─ Process selected streams for encoding
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Interactive Mode
|
||||
```bash
|
||||
python main.py "C:\Videos\Movies" --filter-audio --interactive
|
||||
```
|
||||
|
||||
### Combined with Other Options
|
||||
```bash
|
||||
python main.py "C:\Videos\TV" --filter-audio --interactive --cq 28 --r 1080 --language eng
|
||||
```
|
||||
|
||||
### Interactive Without Explicit --filter-audio
|
||||
```bash
|
||||
python main.py "C:\Videos\Anime" --interactive
|
||||
```
|
||||
(Filtering is auto-enabled with interactive mode)
|
||||
|
||||
## Testing Scenarios
|
||||
|
||||
### Scenario 1: Multiple Audio Languages
|
||||
**Input**: Video with English (stereo), English (5.1), Spanish, Commentary
|
||||
**Expected**: Prompt shows 4 streams, user can select any combination
|
||||
|
||||
### Scenario 2: Invalid Selection
|
||||
**Input**: User types "abc" or non-existent stream number
|
||||
**Expected**: Tool logs warning, keeps all streams, continues
|
||||
|
||||
### Scenario 3: Single Audio Stream
|
||||
**Input**: Video with only one audio track
|
||||
**Expected**: Function returns early, no prompt shown
|
||||
|
||||
### Scenario 4: Empty Input
|
||||
**Input**: User presses Enter without typing
|
||||
**Expected**: All streams kept, confirmation message shown
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
✅ **Fully Backward Compatible**
|
||||
- Existing `--filter-audio` behavior unchanged
|
||||
- New feature is opt-in via `--interactive` flag
|
||||
- Default behavior (no interactive) preserved
|
||||
- No changes to config.xml schema required
|
||||
- All existing scripts/automation continues to work
|
||||
|
||||
## Integration Points
|
||||
|
||||
### With Audio Language Tagging
|
||||
- `--language eng --filter-audio --interactive` works together
|
||||
- User selects streams, then language metadata applied to all
|
||||
|
||||
### With Resolution/CQ Options
|
||||
- `--filter-audio --interactive --cq 28 --r 1080` fully compatible
|
||||
- Interactive selection happens first, encoding follows
|
||||
|
||||
### With Test Mode
|
||||
- `--filter-audio --interactive --test` shows interactive prompt on first file
|
||||
- Useful for testing selections before batch encoding
|
||||
|
||||
## Performance Impact
|
||||
|
||||
✅ **Minimal Impact**
|
||||
- Interactive prompt only appears when user explicitly requests it
|
||||
- No performance overhead when `--interactive` not used
|
||||
- Per-file prompt adds negligible time (user wait for input)
|
||||
- No change to FFmpeg encoding performance
|
||||
|
||||
## Documentation Provided
|
||||
|
||||
1. **INTERACTIVE_AUDIO.md** - User guide with examples
|
||||
2. **IMPLEMENTATION_NOTES.md** - Technical implementation details
|
||||
3. **QUICK_REFERENCE.md** - Quick reference guide and FAQ
|
||||
4. This summary document
|
||||
|
||||
## Completion Checklist
|
||||
|
||||
✅ Function implementation (prompt_user_audio_selection)
|
||||
✅ CLI argument (--interactive)
|
||||
✅ Integration with process_manager
|
||||
✅ Integration with encode_engine
|
||||
✅ Input validation
|
||||
✅ Error handling
|
||||
✅ Logging integration
|
||||
✅ Backward compatibility
|
||||
✅ Documentation
|
||||
✅ Syntax validation
|
||||
✅ Code review
|
||||
|
||||
## Example Output
|
||||
|
||||
When user runs with `--filter-audio --interactive`:
|
||||
|
||||
```
|
||||
================================================================================
|
||||
🎵 AUDIO STREAM SELECTION
|
||||
================================================================================
|
||||
|
||||
Stream #0: 2ch | Lang: eng | Bitrate: 128kbps
|
||||
|
||||
Stream #1: 6ch | Lang: eng | Bitrate: 448kbps
|
||||
|
||||
Stream #2: 2ch | Lang: spa | Bitrate: 128kbps
|
||||
|
||||
Stream #3: 2ch | Lang: comment | Bitrate: 64kbps
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
Enter stream numbers to keep (comma-separated, e.g.: 1,2 or just 2)
|
||||
Leave blank to keep all streams
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
➜ Keep streams: 1,3
|
||||
✅ Keeping 2 stream(s), removing 2 stream(s)
|
||||
|
||||
🎬 Running CQ encode: output.mkv
|
||||
...
|
||||
```
|
||||
|
||||
## Next Steps (Optional Enhancements)
|
||||
|
||||
Future improvements could include:
|
||||
- [ ] Preset buttons for common selections (e.g., "Best Audio", "English Only", "All")
|
||||
- [ ] Auto-numbering display for clarity
|
||||
- [ ] Arrow key selection interface (more interactive)
|
||||
- [ ] Save/load selection templates for batch consistency
|
||||
- [ ] GUI interface for stream selection
|
||||
- [ ] Default selection from config for silent/batch operation
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The interactive audio stream selection feature is **complete and ready for use**. Users can now:
|
||||
|
||||
1. ✅ See all available audio streams with details
|
||||
2. ✅ Choose which streams to keep for encoding
|
||||
3. ✅ Get immediate confirmation of their selection
|
||||
4. ✅ Have per-file control in batch operations
|
||||
5. ✅ Maintain automatic fallback if input is invalid
|
||||
|
||||
The implementation is clean, well-documented, backward-compatible, and fully integrated into the existing codebase.
|
||||
141
IMPLEMENTATION_NOTES.md
Normal file
141
IMPLEMENTATION_NOTES.md
Normal file
@ -0,0 +1,141 @@
|
||||
# Interactive Audio Stream Selection - Implementation Summary
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. New Function: `prompt_user_audio_selection()` in audio_handler.py
|
||||
- **Purpose**: Display audio streams and prompt user for selection
|
||||
- **Input**: List of streams with (index, channels, bitrate, language, metadata)
|
||||
- **Output**: Filtered list containing only user-selected streams
|
||||
- **Features**:
|
||||
- Displays stream info: `Stream #X: YYch | Lang: YYY | Bitrate: XYZkbps`
|
||||
- Accepts comma-separated input: `1,2,3` or `1` or empty (keep all)
|
||||
- Validates input and logs selections
|
||||
- Falls back to keeping all streams on invalid input
|
||||
|
||||
### 2. Updated: `run_ffmpeg()` in encode_engine.py
|
||||
- Now checks `audio_filter_config.get("interactive", False)`
|
||||
- Routes to interactive prompt if `interactive=True`
|
||||
- Routes to automatic filtering if `interactive=False`
|
||||
- Both modes filter streams before codec selection
|
||||
|
||||
### 3. Updated: `process_folder()` in process_manager.py
|
||||
- New parameter: `interactive_audio: bool = False`
|
||||
- Builds audio_filter_config with both `enabled` and `interactive` fields
|
||||
- If `--interactive` used without `--filter-audio`, enables both automatically
|
||||
|
||||
### 4. Updated: main.py
|
||||
- New CLI argument: `--interactive`
|
||||
- Action: `store_true` (binary flag)
|
||||
- Passed through to `process_folder()`
|
||||
- Help text: "Interactive mode: show audio streams and let user select which to keep (requires --filter-audio)"
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: Automatic Filtering (Existing)
|
||||
```bash
|
||||
python main.py "C:\Videos" --filter-audio
|
||||
```
|
||||
- Automatically keeps best English + Commentary
|
||||
- No user interaction
|
||||
|
||||
### Example 2: Interactive Selection (New)
|
||||
```bash
|
||||
python main.py "C:\Videos" --filter-audio --interactive
|
||||
```
|
||||
- Shows each file's audio streams
|
||||
- User picks which streams to keep
|
||||
- Different selections per file allowed
|
||||
|
||||
### Example 3: Interactive Without --filter-audio
|
||||
```bash
|
||||
python main.py "C:\Videos" --interactive
|
||||
```
|
||||
- Same as Example 2 (enables filtering automatically)
|
||||
- More intuitive UX
|
||||
|
||||
## Stream Display Format
|
||||
|
||||
When interactive mode runs, user sees:
|
||||
```
|
||||
================================================================================
|
||||
🎵 AUDIO STREAM SELECTION
|
||||
================================================================================
|
||||
|
||||
Stream #0: 2ch | Lang: eng | Bitrate: 128kbps
|
||||
|
||||
Stream #1: 6ch | Lang: eng | Bitrate: 448kbps
|
||||
|
||||
Stream #2: 2ch | Lang: spa | Bitrate: 128kbps
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
Enter stream numbers to keep (comma-separated, e.g.: 1,2 or just 2)
|
||||
Leave blank to keep all streams
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
➜ Keep streams:
|
||||
```
|
||||
|
||||
## Logging Output
|
||||
|
||||
When user selects streams:
|
||||
```
|
||||
✅ Keeping 2 stream(s), removing 1 stream(s)
|
||||
|
||||
User selected 2 audio stream(s): [1, 2]
|
||||
Removed 1 audio stream(s): [0]
|
||||
```
|
||||
|
||||
## Audio Filter Config Structure
|
||||
|
||||
**Old (Automatic only)**:
|
||||
```python
|
||||
{
|
||||
"enabled": True/False
|
||||
}
|
||||
```
|
||||
|
||||
**New (With Interactive)**:
|
||||
```python
|
||||
{
|
||||
"enabled": True/False,
|
||||
"interactive": True/False
|
||||
}
|
||||
```
|
||||
|
||||
## Flow Diagram
|
||||
|
||||
```
|
||||
main.py
|
||||
└─ parse args (--filter-audio, --interactive)
|
||||
└─ process_folder()
|
||||
└─ for each file:
|
||||
└─ run_ffmpeg()
|
||||
└─ get_audio_streams()
|
||||
└─ if audio_filter_config.enabled:
|
||||
├─ if audio_filter_config.interactive:
|
||||
│ └─ prompt_user_audio_selection() ← NEW
|
||||
│ └─ [User sees streams and selects]
|
||||
└─ else:
|
||||
└─ filter_audio_streams() (automatic)
|
||||
└─ encode with selected streams
|
||||
```
|
||||
|
||||
## Input Validation
|
||||
|
||||
- **Valid**: `1`, `0,1,3`, `2, 3, 5` (spaces OK)
|
||||
- **Invalid**: `abc`, `1.5`, `1-3` (ranges not supported)
|
||||
- **On Invalid**: Keep all streams, log warning
|
||||
|
||||
## Edge Cases Handled
|
||||
|
||||
1. **No streams**: Return original (nothing to filter)
|
||||
2. **Single stream**: Return as-is (no selection needed)
|
||||
3. **Invalid stream indices**: Keep all streams
|
||||
4. **Empty input**: Keep all streams
|
||||
5. **No valid selections**: Keep all streams (with warning)
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
- Existing `--filter-audio` behavior unchanged (automatic mode)
|
||||
- `--interactive` is optional, defaults to False
|
||||
- No breaking changes to config.xml structure
|
||||
- Language tagging (--language) still works alongside audio filtering
|
||||
109
INTERACTIVE_AUDIO.md
Normal file
109
INTERACTIVE_AUDIO.md
Normal file
@ -0,0 +1,109 @@
|
||||
# Interactive Audio Stream Selection
|
||||
|
||||
## Overview
|
||||
The conversion tool now supports **interactive audio stream selection**, allowing you to manually choose which audio tracks to keep during encoding rather than relying on automatic filtering.
|
||||
|
||||
## Usage
|
||||
|
||||
### Enable Interactive Mode
|
||||
Use both `--filter-audio` and `--interactive` flags together:
|
||||
|
||||
```bash
|
||||
python main.py "C:\path\to\videos" --filter-audio --interactive
|
||||
```
|
||||
|
||||
### What Happens
|
||||
When encoding each file with multiple audio streams:
|
||||
|
||||
1. **Audio Stream Display**
|
||||
- The tool displays all available audio streams with details:
|
||||
```
|
||||
🎵 AUDIO STREAM SELECTION
|
||||
================================================================================
|
||||
|
||||
Stream #0: 2ch | Lang: eng | Bitrate: 128kbps
|
||||
|
||||
Stream #1: 6ch | Lang: eng | Bitrate: 448kbps
|
||||
|
||||
Stream #2: 2ch | Lang: spa | Bitrate: 128kbps
|
||||
|
||||
Stream #3: 2ch | Lang: comment | Bitrate: 64kbps
|
||||
```
|
||||
|
||||
2. **User Prompt**
|
||||
- You're asked to select which streams to keep:
|
||||
```
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
Enter stream numbers to keep (comma-separated, e.g.: 1,2 or just 2)
|
||||
Leave blank to keep all streams
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
➜ Keep streams: 1,3
|
||||
```
|
||||
|
||||
3. **Encoding
|
||||
**
|
||||
- Only selected streams are included in the encoded output
|
||||
- Other streams are removed
|
||||
- Selection is logged for reference
|
||||
|
||||
## Input Format
|
||||
|
||||
- **Multiple streams**: `0,1,3` or `0, 1, 3` (spaces optional)
|
||||
- **Single stream**: `1` or `2`
|
||||
- **Keep all**: Press Enter without typing anything
|
||||
|
||||
## Example Scenarios
|
||||
|
||||
### Scenario 1: Keep Main Audio Only
|
||||
```
|
||||
Streams:
|
||||
Stream #0: 2ch (English, 128kbps)
|
||||
Stream #1: 6ch (English Surround, 448kbps) ← Best quality
|
||||
Stream #2: 2ch (Spanish, 128kbps)
|
||||
|
||||
Input: 1
|
||||
|
||||
Result: Only Stream #1 (6ch English Surround) is encoded
|
||||
```
|
||||
|
||||
### Scenario 2: Keep Multiple Languages
|
||||
```
|
||||
Streams:
|
||||
Stream #0: 2ch (English, 128kbps)
|
||||
Stream #1: 6ch (English Surround, 448kbps)
|
||||
Stream #2: 2ch (Spanish, 128kbps)
|
||||
Stream #3: 2ch (Commentary, 64kbps)
|
||||
|
||||
Input: 1,2,3
|
||||
|
||||
Result: Streams #1, #2, and #3 are encoded (English Surround, Spanish, Commentary)
|
||||
```
|
||||
|
||||
## Comparison with Automatic Filtering
|
||||
|
||||
### Automatic Mode (--filter-audio only)
|
||||
- Keeps: Best English audio + all Commentary tracks
|
||||
- No user interaction
|
||||
- Faster batch processing
|
||||
|
||||
### Interactive Mode (--filter-audio --interactive)
|
||||
- Shows all streams and asks user to choose
|
||||
- Per-file control
|
||||
- Better for selective archiving/organization
|
||||
- Useful when automatic filtering doesn't match your preferences
|
||||
|
||||
## Logging
|
||||
|
||||
All user selections are logged to the conversion log for reference:
|
||||
```
|
||||
User selected 2 audio stream(s): [1, 3]
|
||||
Removed 1 audio stream(s): [2]
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Interactive mode requires `--filter-audio` to be enabled
|
||||
- If you use `--interactive` without `--filter-audio`, filtering is automatically enabled
|
||||
- Invalid input (non-existent stream numbers) falls back to keeping all streams
|
||||
- Empty input keeps all audio streams unchanged
|
||||
- The prompt appears for each file being encoded, allowing different selections per file
|
||||
@ -1,7 +1,7 @@
|
||||
# AV1 Batch Video Transcoder - Project Structure
|
||||
|
||||
## Overview
|
||||
A modular batch AV1 video transcoding system using NVIDIA's av1_nvenc codec (10-bit p010le) with intelligent audio/video processing, subtitle embedding, and optional audio language tagging.
|
||||
A modular batch AV1 video transcoding system using NVIDIA's av1_nvenc codec (8-bit yuv420p) with intelligent audio/video processing, subtitle embedding, and optional audio language tagging.
|
||||
|
||||
## Recent Changes (Latest Session)
|
||||
|
||||
@ -64,7 +64,7 @@ A modular batch AV1 video transcoding system using NVIDIA's av1_nvenc codec (10-
|
||||
|
||||
#### `core/encode_engine.py`
|
||||
- **`run_ffmpeg(input_file, output_file, cq, scale_width, scale_height, src_width, src_height, filter_flags, audio_config, method, bitrate_config, subtitle_file, audio_language)`**
|
||||
- Builds FFmpeg command with av1_nvenc codec (preset p1, pix_fmt p010le)
|
||||
- Builds FFmpeg command with av1_nvenc codec (preset p7, pix_fmt yuv420p)
|
||||
- Per-stream audio codec/bitrate decisions
|
||||
- Conditional subtitle input mapping (if subtitle_file provided)
|
||||
- Optional audio language metadata (only if audio_language not None)
|
||||
|
||||
182
QUICK_REFERENCE.md
Normal file
182
QUICK_REFERENCE.md
Normal file
@ -0,0 +1,182 @@
|
||||
# Interactive Audio Selection - Quick Reference
|
||||
|
||||
## Command Syntax
|
||||
|
||||
### Enable Interactive Audio Selection
|
||||
```bash
|
||||
python main.py "C:\path\to\videos" --filter-audio --interactive
|
||||
```
|
||||
|
||||
### Other Flags (Optional)
|
||||
```bash
|
||||
--filter-audio --interactive --cq 28 --r 1080 --language eng --test
|
||||
```
|
||||
|
||||
## What User Sees
|
||||
|
||||
### Per File Prompt (appears for each video)
|
||||
```
|
||||
================================================================================
|
||||
🎵 AUDIO STREAM SELECTION
|
||||
================================================================================
|
||||
|
||||
Stream #0: 2ch | Lang: eng | Bitrate: 128kbps
|
||||
|
||||
Stream #1: 6ch | Lang: eng | Bitrate: 448kbps
|
||||
|
||||
Stream #2: 2ch | Lang: spa | Bitrate: 128kbps
|
||||
|
||||
Stream #3: 2ch | Lang: comment | Bitrate: 64kbps
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
Enter stream numbers to keep (comma-separated, e.g.: 1,2 or just 2)
|
||||
Leave blank to keep all streams
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
➜ Keep streams:
|
||||
```
|
||||
|
||||
### User Input Examples
|
||||
|
||||
| Input | Result |
|
||||
|-------|--------|
|
||||
| `1` | Keep Stream #1 (6ch English, 448kbps) |
|
||||
| `1,3` | Keep Streams #1 and #3 |
|
||||
| `0,1,2` | Keep Streams #0, #1, and #2 |
|
||||
| ` ` (blank) | Keep all 4 streams |
|
||||
|
||||
### Expected Output
|
||||
```
|
||||
✅ Keeping 1 stream(s), removing 3 stream(s)
|
||||
|
||||
🎬 Running CQ encode: output.mkv
|
||||
...
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
✅ **Per-File Control**: Different selections for each video
|
||||
✅ **Clear Display**: See channel count, language, bitrate for each stream
|
||||
✅ **Flexible Input**: Comma-separated numbers, optional spaces
|
||||
✅ **Safe Defaults**: Invalid input keeps all streams
|
||||
✅ **Logging**: All selections recorded in conversion log
|
||||
✅ **Backwards Compatible**: Doesn't break existing workflows
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Movie with Multiple Audio Tracks
|
||||
```
|
||||
Stream #0: 2ch English (128kbps)
|
||||
Stream #1: 6ch English Surround (448kbps) ← Main audio
|
||||
Stream #2: 2ch Spanish (128kbps)
|
||||
Stream #3: 2ch Commentary (64kbps)
|
||||
|
||||
Input: 1,3
|
||||
Output: Keep English 5.1 + Commentary
|
||||
```
|
||||
|
||||
### TV Episode with Multiple Languages
|
||||
```
|
||||
Stream #0: 6ch English (384kbps)
|
||||
Stream #1: 6ch Spanish (384kbps)
|
||||
Stream #2: 2ch Commentary (64kbps)
|
||||
|
||||
Input: 0,1,2
|
||||
Output: Keep all (English, Spanish, Commentary)
|
||||
```
|
||||
|
||||
### File with Only One Audio Track
|
||||
```
|
||||
Stream #0: 6ch English (448kbps)
|
||||
|
||||
Input: (blank or 0)
|
||||
Output: Keep the only track
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: What if I provide an invalid stream number?**
|
||||
A: The tool keeps all streams and logs a warning.
|
||||
|
||||
**Q: Can I specify stream ranges like "0-2"?**
|
||||
A: No, use comma-separated individual numbers: "0,1,2"
|
||||
|
||||
**Q: Do I have to answer the prompt for every file?**
|
||||
A: Yes, this allows different selections per file. Use automatic --filter-audio mode if you want consistent filtering across all files.
|
||||
|
||||
**Q: What happens with invalid input like "abc" or "1.5"?**
|
||||
A: The tool keeps all streams and logs the invalid input. Then continues to the next file.
|
||||
|
||||
**Q: Does --interactive work alone?**
|
||||
A: Yes! If you use --interactive without --filter-audio, filtering is automatically enabled with interactive mode.
|
||||
|
||||
**Q: Can I combine this with --language tagging?**
|
||||
A: Yes! Use: `--filter-audio --interactive --language eng`
|
||||
This lets you select streams AND tag them with language metadata.
|
||||
|
||||
## Integration Points
|
||||
|
||||
### When Called
|
||||
- After audio stream detection in `run_ffmpeg()`
|
||||
- Before codec selection and FFmpeg command building
|
||||
- Only if `audio_filter_config.enabled = True` AND `audio_filter_config.interactive = True`
|
||||
|
||||
### Stream Information Provided
|
||||
- **Index**: Stream number in FFmpeg (0-based)
|
||||
- **Channels**: 2ch, 6ch, etc.
|
||||
- **Language**: eng, spa, und (undefined), etc.
|
||||
- **Bitrate**: Detected bitrate in kbps
|
||||
|
||||
### What Gets Removed
|
||||
- All streams NOT selected by user
|
||||
- Metadata and descriptors for removed streams
|
||||
- No re-encoding of audio (codec decisions apply per stream)
|
||||
|
||||
## Tips & Tricks
|
||||
|
||||
### Keeping Only Surround Audio
|
||||
Most videos have stereo + surround. To keep only 5.1/6ch:
|
||||
```
|
||||
Input: 1 (if Stream #1 is 6ch)
|
||||
```
|
||||
|
||||
### Keeping All Commentary
|
||||
Commentary tracks are usually indexed separately:
|
||||
```
|
||||
Input: 0,2,3 (Stream #0 main + #2 and #3 commentary)
|
||||
```
|
||||
|
||||
### English Only
|
||||
If you have multiple languages:
|
||||
```
|
||||
Input: 0,1 (Stream #0 and #1 English only)
|
||||
```
|
||||
|
||||
## Log Output Examples
|
||||
|
||||
**Successful Selection**
|
||||
```
|
||||
User selected 2 audio stream(s): [1, 3]
|
||||
Removed 1 audio stream(s): [0, 2]
|
||||
```
|
||||
|
||||
**Invalid Input**
|
||||
```
|
||||
User provided invalid audio selection input
|
||||
Keeping all audio streams
|
||||
```
|
||||
|
||||
**No Selection**
|
||||
```
|
||||
Keeping all audio streams
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Issue**: Prompt doesn't appear
|
||||
- **Solution**: Make sure both --filter-audio AND --interactive are specified
|
||||
|
||||
**Issue**: Selection is ignored
|
||||
- **Solution**: Check log file for errors. Verify stream indices exist.
|
||||
|
||||
**Issue**: Want automatic mode back
|
||||
- **Solution**: Use --filter-audio alone (without --interactive)
|
||||
@ -4,8 +4,9 @@ A high-performance batch video transcoding tool using NVIDIA's **AV1 NVENC** cod
|
||||
|
||||
## ✨ Key Features
|
||||
|
||||
- **10-bit AV1 Encoding** - NVIDIA GPU acceleration (p010le, preset p1)
|
||||
- **Smart Audio Processing** - Auto-detects bitrate, downmixes, re-encodes only when needed
|
||||
- **8-bit AV1 Encoding** - NVIDIA GPU acceleration (yuv420p, preset p7)
|
||||
- **Smart Audio Processing** - Auto-detects bitrate, AAC for stereo, EAC3 for 5.1, downmixes, re-encodes only when needed
|
||||
- **Audio Filtering** - Keep only best English audio + Commentary tracks (remove other languages)
|
||||
- **Subtitle Embedding** - Auto-detects and embeds subtitles (.vtt, .srt, .ass, .ssa, .sub)
|
||||
- **Smart Resolution** - Scales 4K→1080p, preserves lower resolutions
|
||||
- **Two-Phase Encoding** - CQ mode first, automatic Bitrate fallback if size threshold exceeded
|
||||
@ -113,7 +114,7 @@ Show.S01E01 - [EHX].mkv (450MB, subtitle embedded, audio tagged)
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Video Codec | AV1 (av1_nvenc) |
|
||||
| Bit Depth | 10-bit (p010le) |
|
||||
| Bit Depth | 8-bit (yuv420p) |
|
||||
| GPU Preset | p1 (high quality) |
|
||||
| Audio Codec | AAC |
|
||||
| Audio Mode | Smart (copy or re-encode) |
|
||||
|
||||
@ -56,7 +56,7 @@ python main.py "P:\tv\Show" --language eng
|
||||
|
||||
## Features
|
||||
|
||||
- **Hardware Encoding**: NVIDIA av1_nvenc (10-bit p010le, preset p1)
|
||||
- **Hardware Encoding**: NVIDIA av1_nvenc (8-bit yuv420p, preset p7)
|
||||
- **Smart Audio**: Analyzes streams, re-encodes excessive bitrate, preserves good quality
|
||||
- **Smart Video**: Detects source resolution, scales 4K→1080p, preserves lower resolutions
|
||||
- **Subtitle Detection**: Auto-finds and embeds subtitles (vtt, srt, ass, ssa, sub)
|
||||
@ -83,7 +83,7 @@ Edit `config.xml` to customize:
|
||||
1. **Detect subtitles**: Looks for matching `.en.vtt`, `.srt`, etc.
|
||||
2. **Analyze source**: Resolution, audio streams, bitrates
|
||||
3. **FFmpeg encode**:
|
||||
- Video: AV1 NVENC (10-bit p010le)
|
||||
- Video: AV1 NVENC (8-bit yuv420p)
|
||||
- Audio: Per-stream decisions (copy or re-encode)
|
||||
- Subtitles: Embedded as SRT (if found)
|
||||
4. **Size check**: Compare output vs original (default 75% threshold)
|
||||
|
||||
33
config.xml
33
config.xml
@ -15,10 +15,10 @@
|
||||
<extensions>.mkv,.mp4</extensions>
|
||||
|
||||
<!-- File name tags to skip/ignore -->
|
||||
<ignore_tags>ehx</ignore_tags> <!-- ,megusta -->
|
||||
<ignore_tags>ehx,._</ignore_tags> <!-- ehx = encoded tag, ._ = macOS metadata files -->
|
||||
|
||||
<!-- Reduction ratio threshold: output must be <= this % of original or encoding fails -->
|
||||
<reduction_ratio_threshold>0.75</reduction_ratio_threshold>
|
||||
<reduction_ratio_threshold>0.85</reduction_ratio_threshold>
|
||||
|
||||
<!-- Subtitle settings -->
|
||||
<subtitles>
|
||||
@ -27,6 +27,13 @@
|
||||
<codec>srt</codec>
|
||||
</subtitles>
|
||||
|
||||
<!-- Audio track filtering: keep only best English audio + Commentary -->
|
||||
<audio_filter>
|
||||
<enabled>false</enabled>
|
||||
<!-- When true: keeps primary English audio (most channels/bitrate) + any Commentary tracks -->
|
||||
<!-- When false: keeps all audio tracks -->
|
||||
</audio_filter>
|
||||
|
||||
<!-- Audio language tag -->
|
||||
<audio_language>eng</audio_language>
|
||||
</general>
|
||||
@ -44,12 +51,24 @@
|
||||
ENCODE SETTINGS
|
||||
============================= -->
|
||||
<encode>
|
||||
<!-- CQ defaults (per resolution / content type) -->
|
||||
<!-- CQ defaults (per resolution / content type / encoder) -->
|
||||
<cq>
|
||||
<tv_1080>30</tv_1080>
|
||||
<tv_720>34</tv_720>
|
||||
<movie_1080>32</movie_1080>
|
||||
<movie_720>34</movie_720>
|
||||
<av1>
|
||||
<tv_1080>32</tv_1080>
|
||||
<tv_720>34</tv_720>
|
||||
<anime_1080>32</anime_1080>
|
||||
<anime_720>34</anime_720>
|
||||
<movie_1080>32</movie_1080>
|
||||
<movie_720>34</movie_720>
|
||||
</av1>
|
||||
<hevc>
|
||||
<tv_1080>28</tv_1080>
|
||||
<tv_720>30</tv_720>
|
||||
<anime_1080>28</anime_1080>
|
||||
<anime_720>30</anime_720>
|
||||
<movie_1080>28</movie_1080>
|
||||
<movie_720>30</movie_720>
|
||||
</hevc>
|
||||
</cq>
|
||||
|
||||
<!-- Fallback bitrate-based mode -->
|
||||
|
||||
@ -504,3 +504,104 @@ tv,Supernatural,Supernatural - S14E13 - Lebanon x265 AC3 Bluray-1080p HiQVE - [E
|
||||
tv,Supernatural,Supernatural - S14E14 - Ouroboros x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1085.86,376.39,34.7,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S14E15 - Peace of Mind x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1209.11,476.01,39.4,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S14E16 - Don't Go in the Woods x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,998.67,353.21,35.4,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S14E17 - Game Night x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1171.18,478.27,40.8,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S14E18 - Absence x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1149.74,496.79,43.2,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S14E19 - Jack in the Box x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1119.57,345.82,30.9,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S14E20 - Moriah x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1194.42,467.3,39.1,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,"Supernatural - S11E01 - Out of the Darkness, Into the Fire x265 AC3 Bluray-1080p HiQVE - [EHX].mkv",1081.05,491.08,45.4,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S11E02 - Form and Void x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1150.4,441.72,38.4,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S11E03 - The Bad Seed x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1000.66,354.67,35.4,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S11E04 - Baby x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1221.59,650.84,53.3,1920x1080,1920x1080,2,34,CQ
|
||||
tv,Supernatural,Supernatural - S11E05 - Thin Lizzie x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1111.38,412.77,37.1,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S11E06 - Our Little World x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1121.15,399.25,35.6,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S11E07 - Plush x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1115.48,444.02,39.8,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S11E08 - Just My Imagination x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1003.32,400.98,40.0,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S11E09 - O Brother Where Art Thou x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1056.77,442.89,41.9,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S11E10 - The Devil in the Details x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1057.23,456.3,43.2,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S11E11 - Into the Mystic x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1115.26,457.89,41.1,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S11E12 - Don’t You Forget about Me x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1054.6,449.94,42.7,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Supernatural,Supernatural - S11E13 - Love Hurts x265 AC3 Bluray-1080p HiQVE - [EHX].mkv,1017.88,371.56,36.5,1920x1080,1920x1080,1,34,CQ
|
||||
tv,Extrapolations,Extrapolations.S01E08.2070.Ecocide.1080p.ATVP.WEB-DL.DDP5.1.H.264-EniaHD - [EHX].mkv,4485.35,821.95,18.3,1920x872,1920x872,2,30,CQ
|
||||
tv,Extrapolations,Extrapolations.S01E07.2068.The.Going-Away.Party.1080p.ATVP.WEB-DL.DDP5.1.H.264-EniaHD - [EHX].mkv,3977.53,517.86,13.0,1920x872,1920x872,2,30,CQ
|
||||
tv,Extrapolations,Extrapolations.S01E04.2059.Face.of.God.1080p.ATVP.WEB-DL.DDP5.1.H.264-EniaHD - [EHX].mkv,4348.62,655.87,15.1,1920x872,1920x872,2,30,CQ
|
||||
tv,Extrapolations,Extrapolations.S01E05.2059.Part.II.Nightbirds.1080p.ATVP.WEB-DL.DDP5.1.H.264-EniaHD - [EHX].mkv,4358.04,875.13,20.1,1920x872,1920x872,2,30,CQ
|
||||
tv,Extrapolations,Extrapolations.S01E02.2046.Whale.Fall.1080p.ATVP.WEB-DL.DDP5.1.H.264-EniaHD - [EHX].mkv,4506.01,919.87,20.4,1920x872,1920x872,2,30,CQ
|
||||
tv,Extrapolations,Extrapolations.S01E06.2066.Lola.1080p.ATVP.WEB-DL.DDP5.1.H.264-EniaHD - [EHX].mkv,5080.34,886.64,17.5,1920x872,1920x872,2,30,CQ
|
||||
tv,Extrapolations,Extrapolations.S01E03.2047.The.Fifth.Question.1080p.ATVP.WEB-DL.DDP5.1.H.264-EniaHD - [EHX].mkv,4775.68,964.11,20.2,1920x872,1920x872,2,30,CQ
|
||||
tv,Extrapolations,Extrapolations.S01E01.2037.A.Raven.Story.1080p.ATVP.WEB-DL.DDP5.1.H.264-EniaHD - [EHX].mkv,4308.18,1049.28,24.4,1920x872,1920x872,2,30,CQ
|
||||
movie,N/A,Superman (2025) x265 EAC3 7.1 Bluray-1080p Ghost - [EHX].mkv,8364.57,4374.49,52.3,1920x1080,1920x1080,4,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),[sam] Vanitas no Carte - 02 [BD 1080p FLAC] [8822B4BC] - [EHX].mkv,1662.26,248.98,15.0,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),[sam] Vanitas no Carte - 03 [BD 1080p FLAC] [BDE63D2B] - [EHX].mkv,1540.47,251.65,16.3,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),[sam] Vanitas no Carte - 04 [BD 1080p FLAC] [4B388837] - [EHX].mkv,1841.37,303.34,16.5,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),[sam] Vanitas no Carte - 05 [BD 1080p FLAC] [03D15E74] - [EHX].mkv,1533.1,279.44,18.2,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),[sam] Vanitas no Carte - 06 [BD 1080p FLAC] [8498E1EE] - [EHX].mkv,1851.33,343.69,18.6,1920x1080,1920x1080,2,32,CQ
|
||||
movie,N/A,xXx - Return of Xander Cage (2017) x264 TrueHD Atmos 7.1 Bluray-1080p DDR - [EHX].mkv,14066.19,3230.54,23.0,1920x800,1920x800,3,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),[sam] Vanitas no Carte - 07 [BD 1080p FLAC] [B24C2A72] - [EHX].mkv,1252.42,256.39,20.5,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),[sam] Vanitas no Carte - 08 [BD 1080p FLAC] [133E6216] - [EHX].mkv,1340.99,214.87,16.0,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),[sam] Vanitas no Carte - 09 [BD 1080p FLAC] [90B63B29] - [EHX].mkv,1431.18,246.63,17.2,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),[sam] Vanitas no Carte - 10 [BD 1080p FLAC] [9EB21FD3] - [EHX].mkv,1478.88,280.75,19.0,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),[sam] Vanitas no Carte - 11 [BD 1080p FLAC] [39EC1E6A] - [EHX].mkv,1271.25,245.56,19.3,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),[sam] Vanitas no Carte - 12 [BD 1080p FLAC] [41A14681] - [EHX].mkv,1557.74,243.59,15.6,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),The Case Study of Vanitas - S01E01 - Mémoire 1 - Vanitas ―In the Event of Rusty Hopes― x265 FLAC Bluray-1080p sam - [EHX].mkv,2254.26,369.91,16.4,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),The Case Study of Vanitas - S01E13 - Mémoire 13 - Forêt d'argent ―A Chance Encounter― x265 FLAC Bluray-1080p sam - [EHX].mkv,1769.7,384.47,21.7,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),The Case Study of Vanitas - S01E14 - Mémoire 14 - Château de sorciére ―The Witch and the Young Man― x265 FLAC Bluray-1080p sam - [EHX].mkv,1526.89,251.6,16.5,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),The Case Study of Vanitas - S01E15 - Mémoire 15 - Oiseau et ciel ―The d'Apchiers' Vampire― x265 FLAC Bluray-1080p sam - [EHX].mkv,1659.94,288.39,17.4,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),The Case Study of Vanitas - S01E16 - Mémoire 16 - Chasse aux vampires ―The Beast― x265 FLAC Bluray-1080p sam - [EHX].mkv,1893.39,312.18,16.5,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),The Case Study of Vanitas - S01E17 - Mémoire 17 - Vengeance ―Hands Upon a Nightmare― x265 FLAC Bluray-1080p sam - [EHX].mkv,2111.03,365.15,17.3,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),The Case Study of Vanitas - S01E18 - Mémoire 18 - Avec toi ―Just the Two of Us― x265 FLAC Bluray-1080p sam - [EHX].mkv,1620.1,286.11,17.7,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),The Case Study of Vanitas - S01E19 - Mémoire 19 - Canorus ―Snow Flower― x265 FLAC Bluray-1080p sam - [EHX].mkv,1876.92,395.46,21.1,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),The Case Study of Vanitas - S01E20 - Mémoire 20 - Mal d'amour ―The Incurable Disease― x265 FLAC Bluray-1080p sam - [EHX].mkv,1336.24,234.84,17.6,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),The Case Study of Vanitas - S01E21 - Mémoire 21 - Un autre ―Scar― x265 FLAC Bluray-1080p sam - [EHX].mkv,1359.08,229.23,16.9,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),The Case Study of Vanitas - S01E22 - Mémoire 22 - Rencontre ―Blue Night― x265 FLAC Bluray-1080p sam - [EHX].mkv,1448.32,223.05,15.4,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),The Case Study of Vanitas - S01E23 - Mémoire 23 - Pleuvoir ―Tears like Rain― x265 FLAC Bluray-1080p sam - [EHX].mkv,2601.41,396.97,15.3,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),The Case Study of Vanitas - S01E24 - Mémoire 24 - Après la pluie ―His Wish― x265 FLAC Bluray-1080p sam - [EHX].mkv,1991.11,303.87,15.3,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),[sam] Vanitas no Carte - NCED 1 [BD 1080p FLAC] [F919673C] - [EHX].mkv,115.5,10.38,9.0,1920x1080,1920x1080,2,32,CQ
|
||||
anime,The Case Study of Vanitas (2021),[sam] Vanitas no Carte - NCOP 1 [BD 1080p FLAC] [ACE65BAF] - [EHX].mkv,99.75,21.82,21.9,1920x1080,1920x1080,1,32,CQ
|
||||
anime,Sword of the Demon Hunter - Kijin Gentosho (2025),Sword of the Demon Hunter - Kijin Gentosho - S01E01 - Demons and Humans x264 AAC WEBDL-1080p VARYG - [EHX].mkv,2230.88,482.25,21.6,1920x1080,1920x1080,2,32,CQ
|
||||
anime,Sword of the Demon Hunter - Kijin Gentosho (2025),Sword of the Demon Hunter - Kijin Gentosho - S01E02 - The Demon's Daughter x264 AAC WEBDL-1080p VARYG - [EHX].mkv,975.24,165.89,17.0,1920x1080,1920x1080,2,32,CQ
|
||||
anime,Sword of the Demon Hunter - Kijin Gentosho (2025),Sword of the Demon Hunter - Kijin Gentosho - S01E03 - The Devourer (Part 1) x264 AAC WEBDL-1080p VARYG - [EHX].mkv,992.22,169.73,17.1,1920x1080,1920x1080,2,32,CQ
|
||||
anime,Sword of the Demon Hunter - Kijin Gentosho (2025),Sword of the Demon Hunter - Kijin Gentosho - S01E04 - The Devourer (Part 2) x264 AAC WEBDL-1080p VARYG - [EHX].mkv,991.38,174.69,17.6,1920x1080,1920x1080,2,32,CQ
|
||||
anime,Sword of the Demon Hunter - Kijin Gentosho (2025),Sword of the Demon Hunter - Kijin Gentosho - S01E05 - The Garden of Happiness (Part 1) x264 AAC WEBDL-1080p VARYG - [EHX].mkv,990.18,199.98,20.2,1920x1080,1920x1080,2,32,CQ
|
||||
anime,Sword of the Demon Hunter - Kijin Gentosho (2025),Sword of the Demon Hunter - Kijin Gentosho - S01E06 - The Garden of Happiness (Part 2) x264 AAC WEBDL-1080p VARYG - [EHX].mkv,988.98,203.36,20.6,1920x1080,1920x1080,2,32,CQ
|
||||
anime,Sword of the Demon Hunter - Kijin Gentosho (2025),Sword of the Demon Hunter - Kijin Gentosho - S01E07 - The Haunting of Kudanzaka x264 AAC WEBDL-1080p VARYG - [EHX].mkv,989.27,168.54,17.0,1920x1080,1920x1080,2,32,CQ
|
||||
anime,Sword of the Demon Hunter - Kijin Gentosho (2025),Sword of the Demon Hunter - Kijin Gentosho - S01E08 - Blossoming Dreams of the Kanzashi (Part 1) x264 AAC WEBDL-1080p VARYG - [EHX].mkv,989.56,208.83,21.1,1920x1080,1920x1080,2,32,CQ
|
||||
anime,Sword of the Demon Hunter - Kijin Gentosho (2025),Sword of the Demon Hunter - Kijin Gentosho - S01E09 - Blossoming Dreams of the Kanzashi (Part 2) x264 AAC WEBDL-1080p VARYG - [EHX].mkv,988.53,204.37,20.7,1920x1080,1920x1080,2,32,CQ
|
||||
anime,Sword of the Demon Hunter - Kijin Gentosho (2025),Sword of the Demon Hunter - Kijin Gentosho - S01E10 - Harlot in the Rain x264 AAC WEBDL-1080p VARYG - [EHX].mkv,990.33,202.56,20.5,1920x1080,1920x1080,2,32,CQ
|
||||
anime,Sword of the Demon Hunter - Kijin Gentosho (2025),Sword of the Demon Hunter - Kijin Gentosho - S01E11 - Drunken Dreams of Lingering Snow (Part 1) x264 AAC WEBDL-1080p VARYG - [EHX].mkv,992.92,172.67,17.4,1920x1080,1920x1080,2,32,CQ
|
||||
anime,Sword of the Demon Hunter - Kijin Gentosho (2025),Sword of the Demon Hunter - Kijin Gentosho - S01E12 - Drunken Dreams of Lingering Snow (Part 2) x264 AAC WEBDL-1080p VARYG - [EHX].mkv,989.34,199.49,20.2,1920x1080,1920x1080,2,32,CQ
|
||||
movie,N/A,A New Era꞉ DC Takes Off.mkv,151.36,57.92,38.3,1920x1080,1280x720,1,34,CQ
|
||||
movie,N/A,Adventures in the Making of “Superman”.mkv,1885.82,608.37,32.3,1920x1080,1280x720,1,34,CQ
|
||||
movie,N/A,Breaking News꞉ The Daily Planet Returns.mkv,174.45,70.86,40.6,1920x1080,1280x720,1,34,CQ
|
||||
movie,N/A,Icons Forever꞉ Superman’s Enduring Legacy.mkv,196.44,73.29,37.3,1920x1080,1280x720,1,34,CQ
|
||||
movie,N/A,Krypto Short꞉ School Bus Scuffle.mkv,169.9,55.95,32.9,1920x1080,1280x720,1,34,CQ
|
||||
movie,N/A,Kryptunes꞉ The Music of “Superman”.mkv,209.9,75.31,35.9,1920x1080,1280x720,1,34,CQ
|
||||
movie,N/A,Lex Luthor꞉ The Mind of a Master Villain.mkv,172.32,62.11,36.0,1920x1080,1280x720,1,34,CQ
|
||||
movie,N/A,Pawns to Pixels꞉ Krypto Is Born.mkv,185.04,69.84,37.7,1920x1080,1280x720,1,34,CQ
|
||||
movie,N/A,The Justice Gang.mkv,338.06,114.74,33.9,1920x1080,1280x720,1,34,CQ
|
||||
movie,N/A,The Ultimate Villain.mkv,164.36,74.57,45.4,1920x1080,1280x720,1,34,CQ
|
||||
movie,N/A,2026-01-03 09-23-11 - [EHX].mkv,845.34,133.54,15.8,3840x2160,1920x1080,1,32,CQ
|
||||
movie,N/A,Pirates of the Caribbean - Dead Man's Chest (2006) (1080p BluRay x265 10bit Tigole) - [EHX].mkv,9134.59,4091.91,44.8,1920x806,1920x806,2,27,CQ
|
||||
movie,N/A,Pirates of the Caribbean - Dead Man's Chest (2006) (1080p BluRay x265 10bit Tigole) - Copy - [EHX].mkv,9134.59,3632.85,39.8,1920x806,1920x806,0,28,CQ
|
||||
movie,N/A,Pirates of the Caribbean - At World's End (2007) (1080p BluRay x265 10bit Tigole) - [EHX].mkv,9795.92,3764.59,38.4,1920x800,1920x800,1,28,CQ
|
||||
movie,N/A,Pirates of the Caribbean - The Curse of the Black Pearl (2003) (1080p BluRay x265 10bit Tigole) - [EHX].mkv,8626.32,3549.92,41.2,1920x800,1920x800,4,28,CQ
|
||||
movie,N/A,Pirates of the Caribbean - Dead Men Tell No Tales (2017) (1080p BluRay x265 10bit Tigole) - [EHX].mkv,8415.25,2889.53,34.3,1920x800,1920x800,1,28,CQ
|
||||
movie,N/A,Deleted Scenes.mkv,67.72,51.41,75.9,1920x800,1920x800,1,28,CQ
|
||||
movie,N/A,Gallery - Jerry Bruckheimer Photo Diary.mkv,33.16,25.72,77.6,1920x1080,1920x1080,1,28,CQ
|
||||
movie,N/A,Pirates of the Caribbean - On Stranger Tides (2011) (1080p BluRay x265 10bit Tigole) - [EHX].mkv,7070.29,2401.09,34.0,1920x800,1920x800,2,28,CQ
|
||||
tv,Dimension 20,Dimension 20 - S27E01 - Welcome to the Wastes - [EHX].mkv,3929.25,1665.15,42.4,1920x1080,1920x1080,1,32,CQ
|
||||
movie,N/A,Superman (2025) x265 EAC3 7.1 Bluray-1080p Ghost - Copy - [EHX].mkv,8364.57,3891.43,46.5,1920x1080,1920x1080,4,28,CQ
|
||||
movie,N/A,The Roundup (2022) x265 AAC 5.1 Bluray-1080p Tigole - [EHX].mkv,5702.94,2383.35,41.8,1920x804,1920x804,2,28,CQ
|
||||
movie,N/A,Wolf Children (2012) x264 AC3 5.1 Bluray-1080p RH - [EHX].mkv,4453.91,1929.93,43.3,1920x1080,1920x1080,3,32,CQ
|
||||
movie,N/A,The Intern (2015) x265 AAC 5.1 Bluray-1080p Tigole - [EHX].mkv,5020.75,2681.95,53.4,1920x1080,1920x1080,1,28,CQ
|
||||
movie,N/A,The.Suicide.Squad.2021.1080p.HMAX.WEB-DL.DDP5.1.Atmos.X.264-EVO - [EHX].mkv,5220.52,3193.39,61.2,1920x1012,1920x1012,1,32,CQ
|
||||
movie,N/A,Venom - The Last Dance (2024) x265 EAC3 5.1 Bluray-1080p Radarr - [EHX].mkv,5722.93,1798.15,31.4,1920x804,1920x804,1,28,CQ
|
||||
movie,N/A,Meet the Fockers (2004) 1080p 10bit Bluray x265 HEVC [Org DD 5.1 Hindi + DD 5.1 English] MSubs ~ TombDoc - [EHX].mkv,4453.37,1639.66,36.8,1920x1080,1280x720,2,30,CQ
|
||||
movie,N/A,Meet the Parents (2000) 1080p 10bit Bluray x265 HEVC [Org DD 5.1 Hindi + DD 5.1 English] MSubs ~ TombDoc - [EHX].mkv,4190.09,1167.67,27.9,1920x1080,1280x720,2,30,CQ
|
||||
movie,N/A,Little Fockers (2010) 1080p 10bit Bluray x265 HEVC [Org DD 5.1 Hindi + DD 5.1 English] MSubs ~ TombDoc - [EHX].mkv,3753.92,934.35,24.9,1920x1040,1280x720,2,30,CQ
|
||||
movie,N/A,Premium Rush (2012) x265 AAC 5.1 Bluray-1080p afm72 - [EHX].mkv,4184.15,2174.93,52.0,1920x800,1920x1080,1,28,CQ
|
||||
movie,N/A,Captain America - Brave New World (2025) x265 EAC3 7.1 Bluray-1080p Silence - [EHX].mkv,5999.46,2240.25,37.3,1920x800,1920x800,3,28,CQ
|
||||
movie,N/A,A New Era꞉ DC Takes Off.mkv,151.36,112.09,74.1,1920x1080,1920x1080,1,28,CQ
|
||||
movie,N/A,Adventures in the Making of “Superman”.mkv,1885.82,1191.9,63.2,1920x1080,1920x1080,1,28,CQ
|
||||
movie,N/A,Lex Luthor꞉ The Mind of a Master Villain.mkv,172.32,122.45,71.1,1920x1080,1920x1080,1,28,CQ
|
||||
movie,N/A,Kryptunes꞉ The Music of “Superman”.mkv,209.9,155.56,74.1,1920x1080,1920x1080,1,28,CQ
|
||||
movie,N/A,Breaking News꞉ The Daily Planet Returns.mkv,174.45,139.55,80.0,1920x1080,1920x1080,1,28,CQ
|
||||
|
||||
|
Can't render this file because it has a wrong number of fields in line 14.
|
@ -42,8 +42,8 @@ def calculate_stream_bitrate(input_file: Path, stream_index: int) -> int:
|
||||
str(input_file)
|
||||
]
|
||||
try:
|
||||
probe_result = subprocess.run(probe_cmd, capture_output=True, text=True, check=False)
|
||||
codec_name = probe_result.stdout.strip().lower() if probe_result.returncode == 0 else "aac"
|
||||
probe_result = subprocess.run(probe_cmd, capture_output=True, text=True, encoding='utf-8', errors='ignore', check=False)
|
||||
codec_name = probe_result.stdout.strip().lower() if probe_result.stdout and probe_result.returncode == 0 else "aac"
|
||||
except:
|
||||
codec_name = "aac"
|
||||
|
||||
@ -64,7 +64,7 @@ def calculate_stream_bitrate(input_file: Path, stream_index: int) -> int:
|
||||
temp_audio_path
|
||||
]
|
||||
logger.debug(f"Extracting audio stream {stream_index} ({codec_name}) to temporary file for bitrate calculation...")
|
||||
result = subprocess.run(extract_cmd, capture_output=True, text=True, check=False)
|
||||
result = subprocess.run(extract_cmd, capture_output=True, text=True, encoding='utf-8', errors='ignore', check=False)
|
||||
|
||||
# Check if extraction succeeded
|
||||
if result.returncode != 0:
|
||||
@ -76,7 +76,8 @@ def calculate_stream_bitrate(input_file: Path, stream_index: int) -> int:
|
||||
# Step 2: Parse bitrate from ffmpeg's output (stderr)
|
||||
# Look for line like: "bitrate= 457.7kbits/s"
|
||||
bitrate_kbps = 0
|
||||
for line in result.stderr.split("\n"):
|
||||
stderr_lines = result.stderr if result.stderr else ""
|
||||
for line in stderr_lines.split("\n"):
|
||||
if "bitrate=" in line:
|
||||
# Extract bitrate value from line like "size= 352162KiB time=01:45:03.05 bitrate= 457.7kbits/s"
|
||||
parts = line.split("bitrate=")
|
||||
@ -101,11 +102,14 @@ def calculate_stream_bitrate(input_file: Path, stream_index: int) -> int:
|
||||
"-of", "default=noprint_wrappers=1:nokey=1:noprint_wrappers=1",
|
||||
temp_audio_path
|
||||
]
|
||||
duration_result = subprocess.run(duration_cmd, capture_output=True, text=True, check=True)
|
||||
duration_seconds = float(duration_result.stdout.strip())
|
||||
|
||||
bitrate_kbps = int((file_size_bytes * 8) / duration_seconds / 1000)
|
||||
logger.debug(f"Stream {stream_index}: Calculated bitrate from file: {bitrate_kbps} kbps")
|
||||
duration_result = subprocess.run(duration_cmd, capture_output=True, text=True, encoding='utf-8', errors='ignore', check=False)
|
||||
try:
|
||||
duration_seconds = float(duration_result.stdout.strip()) if duration_result.stdout else 1.0
|
||||
bitrate_kbps = int((file_size_bytes * 8) / duration_seconds / 1000)
|
||||
logger.debug(f"Stream {stream_index}: Calculated bitrate from file: {bitrate_kbps} kbps")
|
||||
except (ValueError, ZeroDivisionError):
|
||||
logger.warning(f"Stream {stream_index}: Could not parse duration from ffprobe")
|
||||
return 0
|
||||
|
||||
return bitrate_kbps
|
||||
|
||||
@ -126,21 +130,55 @@ def calculate_stream_bitrate(input_file: Path, stream_index: int) -> int:
|
||||
def get_audio_streams(input_file: Path):
|
||||
"""
|
||||
Detect audio streams and calculate robust bitrates by extracting each stream.
|
||||
Returns list of (index, channels, calculated_bitrate_kbps, language, metadata_bitrate_kbps)
|
||||
Returns list of (index, channels, calculated_bitrate_kbps, language, metadata_bitrate_kbps, title)
|
||||
"""
|
||||
import re
|
||||
|
||||
# First, get full ffprobe output to extract language codes and titles
|
||||
probe_cmd = ["ffprobe", "-v", "info", str(input_file)]
|
||||
probe_result = subprocess.run(probe_cmd, capture_output=True, text=True, encoding='utf-8', errors='ignore')
|
||||
|
||||
# Parse language and title from output
|
||||
language_map = {}
|
||||
title_map = {}
|
||||
|
||||
stderr_output = probe_result.stderr if probe_result.stderr else ""
|
||||
for line in stderr_output.split("\n"):
|
||||
# Match "Stream #0:X(YYY)" where X is stream number, YYY is language
|
||||
match = re.search(r"Stream #0:(\d+)\((\w{3})\)", line)
|
||||
if match:
|
||||
stream_idx = int(match.group(1))
|
||||
lang_code = match.group(2)
|
||||
language_map[stream_idx] = lang_code
|
||||
|
||||
# Get audio stream details via JSON with tags
|
||||
cmd = [
|
||||
"ffprobe","-v","error","-select_streams","a",
|
||||
"-show_entries","stream=index,channels,bit_rate,tags=language",
|
||||
"-show_entries","stream=index,channels,bit_rate,tags",
|
||||
"-of","json", str(input_file)
|
||||
]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
data = json.loads(result.stdout)
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='ignore')
|
||||
try:
|
||||
data = json.loads(result.stdout) if result.stdout else {"streams": []}
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data = {"streams": []}
|
||||
|
||||
streams = []
|
||||
|
||||
for stream_num, s in enumerate(data.get("streams", [])):
|
||||
index = s["index"]
|
||||
channels = s.get("channels", 2)
|
||||
src_lang = s.get("tags", {}).get("language", "und")
|
||||
|
||||
# Get language from our parsed map, default to "und"
|
||||
src_lang = language_map.get(index, "und")
|
||||
|
||||
# Get title from tags or from our parsed map
|
||||
title = ""
|
||||
if "tags" in s and "title" in s["tags"]:
|
||||
title = s["tags"]["title"]
|
||||
elif index in title_map:
|
||||
title = title_map[index]
|
||||
|
||||
bit_rate_meta = int(s.get("bit_rate", 0)) if s.get("bit_rate") else 0
|
||||
|
||||
# Calculate robust bitrate by extracting the audio stream
|
||||
@ -151,7 +189,7 @@ def get_audio_streams(input_file: Path):
|
||||
calculated_bitrate_kbps = int(bit_rate_meta / 1000) if bit_rate_meta else 160
|
||||
logger.info(f"Stream {index}: Using fallback bitrate {calculated_bitrate_kbps} kbps")
|
||||
|
||||
streams.append((index, channels, calculated_bitrate_kbps, src_lang, int(bit_rate_meta / 1000) if bit_rate_meta else 0))
|
||||
streams.append((index, channels, calculated_bitrate_kbps, src_lang, int(bit_rate_meta / 1000) if bit_rate_meta else 0, title))
|
||||
|
||||
return streams
|
||||
|
||||
@ -216,3 +254,188 @@ def choose_audio_bitrate(channels: int, bitrate_kbps: int, audio_config: dict, i
|
||||
else:
|
||||
# Medium and above, use medium with EAC3
|
||||
return ("eac3", medium_br)
|
||||
|
||||
def filter_audio_streams(input_file: Path, streams: list) -> list:
|
||||
"""
|
||||
Filter audio streams to keep only best English audio + Commentary tracks.
|
||||
|
||||
Args:
|
||||
input_file: Path to video file
|
||||
streams: List of (index, channels, bitrate, language, metadata, title) tuples
|
||||
|
||||
Returns:
|
||||
Filtered list of streams (original indices preserved for FFmpeg mapping)
|
||||
"""
|
||||
if not streams:
|
||||
return streams
|
||||
|
||||
# Try to get stream metadata (title) to detect commentary
|
||||
english_tracks = []
|
||||
commentary_tracks = []
|
||||
|
||||
for stream_info in streams:
|
||||
index, channels, bitrate, language, metadata, title = stream_info
|
||||
|
||||
# Check if commentary (in title or metadata)
|
||||
is_commentary = "comment" in str(title).lower() or "comment" in str(metadata).lower()
|
||||
|
||||
# Determine if English (check language field or assume first is English if no language set)
|
||||
is_english = (language and "eng" in language.lower()) or (not language)
|
||||
|
||||
if is_commentary:
|
||||
commentary_tracks.append((index, channels, bitrate, stream_info))
|
||||
elif is_english:
|
||||
english_tracks.append((index, channels, bitrate, stream_info))
|
||||
|
||||
# If no English tracks, return original
|
||||
if not english_tracks:
|
||||
logger.info("No English audio tracks detected - keeping all audio")
|
||||
return streams
|
||||
|
||||
# Pick best English track (most channels, then highest bitrate)
|
||||
english_tracks.sort(key=lambda x: (-x[1], -x[2])) # Sort by channels desc, then bitrate desc
|
||||
best_english = english_tracks[0][3] # Get original stream tuple
|
||||
|
||||
logger.info(f"Audio filter: Keeping best English track (index {best_english[0]}: {best_english[1]}ch @ {best_english[2]}kbps)")
|
||||
|
||||
# Build result: best English + all commentary
|
||||
filtered = [best_english] + [ct[3] for ct in commentary_tracks]
|
||||
|
||||
if commentary_tracks:
|
||||
logger.info(f"Audio filter: Also keeping {len(commentary_tracks)} commentary track(s)")
|
||||
|
||||
# Log removed tracks
|
||||
removed_count = len(streams) - len(filtered)
|
||||
if removed_count > 0:
|
||||
logger.info(f"Audio filter: Removed {removed_count} non-English audio track(s)")
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
def prompt_user_audio_selection(streams: list) -> list:
|
||||
"""
|
||||
Interactively prompt user to select which audio streams to keep.
|
||||
|
||||
Args:
|
||||
streams: List of (index, channels, bitrate, language, metadata, title) tuples
|
||||
|
||||
Returns:
|
||||
Filtered list containing only selected streams
|
||||
"""
|
||||
if not streams or len(streams) <= 1:
|
||||
return streams
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("🎵 AUDIO STREAM SELECTION")
|
||||
print("="*80)
|
||||
|
||||
# Display all streams with details
|
||||
for index, channels, bitrate, language, metadata, title in streams:
|
||||
channels_display = f"{channels}ch"
|
||||
lang_display = language if language != "und" else "undefined"
|
||||
|
||||
# Display title if available
|
||||
if title:
|
||||
title_display = f" | {title}"
|
||||
else:
|
||||
title_display = ""
|
||||
|
||||
print(f"\nStream #{index}: {channels_display} | Lang: {lang_display} | Bitrate: {bitrate}kbps{title_display}")
|
||||
|
||||
print("\n" + "-"*80)
|
||||
print("Enter stream numbers to keep (comma-separated, e.g.: 1,2 or just 2)")
|
||||
print("Leave blank to keep all streams")
|
||||
print("-"*80)
|
||||
|
||||
user_input = input("➜ Keep streams: ").strip()
|
||||
|
||||
# If empty, keep all
|
||||
if not user_input:
|
||||
print("✅ Keeping all audio streams\n")
|
||||
return streams
|
||||
|
||||
# Parse user input
|
||||
try:
|
||||
selected_indices = set()
|
||||
for part in user_input.split(","):
|
||||
idx = int(part.strip())
|
||||
selected_indices.add(idx)
|
||||
except ValueError:
|
||||
print("❌ Invalid input. Keeping all streams.")
|
||||
logger.warning("User provided invalid audio selection input")
|
||||
return streams
|
||||
|
||||
# Filter streams to only selected ones
|
||||
filtered = [s for s in streams if s[0] in selected_indices]
|
||||
|
||||
if not filtered:
|
||||
print("❌ No valid streams selected. Keeping all streams.")
|
||||
logger.warning("User selected no valid streams")
|
||||
return streams
|
||||
|
||||
# Log what was selected/removed
|
||||
removed_count = len(streams) - len(filtered)
|
||||
print(f"✅ Keeping {len(filtered)} stream(s), removing {removed_count} stream(s)\n")
|
||||
logger.info(f"User selected {len(filtered)} audio stream(s): {[s[0] for s in filtered]}")
|
||||
|
||||
if removed_count > 0:
|
||||
removed_indices = [s[0] for s in streams if s[0] not in selected_indices]
|
||||
logger.info(f"Removed {removed_count} audio stream(s): {removed_indices}")
|
||||
|
||||
# Return filtered streams without strip_title field - let prompt_for_title_stripping handle that
|
||||
return filtered
|
||||
|
||||
|
||||
def prompt_for_title_stripping(filtered_streams: list) -> list:
|
||||
"""
|
||||
Prompt user to select which streams should have titles stripped.
|
||||
|
||||
Args:
|
||||
filtered_streams: List of (index, channels, bitrate, language, metadata, title, strip_title) tuples
|
||||
|
||||
Returns:
|
||||
Same list with strip_title field updated based on user selection
|
||||
"""
|
||||
streams_with_titles = [(s[0], s[5]) for s in filtered_streams if s[5]]
|
||||
|
||||
if not streams_with_titles:
|
||||
return [s + (False,) if len(s) == 6 else s for s in filtered_streams]
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("📝 TITLE METADATA STRIPPING (Optional)")
|
||||
print("="*80)
|
||||
print("\nStreams with titles that can be stripped:\n")
|
||||
|
||||
for idx, title in streams_with_titles:
|
||||
print(f" Stream #{idx}: \"{title}\"")
|
||||
|
||||
print("\n" + "-"*80)
|
||||
print("Enter stream numbers to STRIP titles (comma-separated, or leave blank to keep all)")
|
||||
print("Example: \"1,3\" will strip titles from streams #1 and #3")
|
||||
print("-"*80)
|
||||
|
||||
strip_input = input("➜ Strip titles from: ").strip()
|
||||
|
||||
strip_indices = set()
|
||||
if strip_input:
|
||||
try:
|
||||
for part in strip_input.split(","):
|
||||
idx = int(part.strip())
|
||||
strip_indices.add(idx)
|
||||
except ValueError:
|
||||
print("❌ Invalid input. Keeping all titles.\n")
|
||||
logger.warning("Invalid title stripping input")
|
||||
|
||||
# Add strip_title field to each stream
|
||||
result = []
|
||||
for s in filtered_streams:
|
||||
should_strip = s[0] in strip_indices
|
||||
result.append(s + (should_strip,))
|
||||
|
||||
if strip_indices:
|
||||
print(f"✅ Will strip titles from stream(s): {sorted(list(strip_indices))}\n")
|
||||
logger.info(f"User selected to strip titles from streams: {sorted(list(strip_indices))}")
|
||||
else:
|
||||
print("✅ Keeping all titles\n")
|
||||
|
||||
return result
|
||||
@ -91,35 +91,47 @@ def load_config_xml(path: Path) -> dict:
|
||||
if encode_elem is not None:
|
||||
cq_elem = encode_elem.find("cq")
|
||||
if cq_elem is not None:
|
||||
for child in cq_elem:
|
||||
if child.text:
|
||||
cq[child.tag] = int(child.text)
|
||||
# Check if CQ has encoder-specific sub-elements (av1, hevc)
|
||||
encoder_elems = list(cq_elem)
|
||||
if encoder_elems and encoder_elems[0].tag in ["av1", "hevc"]:
|
||||
# New nested structure with encoder-specific CQ values
|
||||
for encoder_tag in cq_elem:
|
||||
if encoder_tag.tag in ["av1", "hevc"]:
|
||||
cq[encoder_tag.tag] = {}
|
||||
for child in encoder_tag:
|
||||
if child.text and child.text.strip():
|
||||
cq[encoder_tag.tag][child.tag] = int(child.text.strip())
|
||||
else:
|
||||
# Old flat structure (backwards compatibility)
|
||||
for child in cq_elem:
|
||||
if child.text and child.text.strip():
|
||||
cq[child.tag] = int(child.text.strip())
|
||||
|
||||
fallback_elem = encode_elem.find("fallback")
|
||||
if fallback_elem is not None:
|
||||
for child in fallback_elem:
|
||||
if child.text:
|
||||
fallback[child.tag] = child.text
|
||||
if child.text and child.text.strip():
|
||||
fallback[child.tag] = child.text.strip()
|
||||
|
||||
filters_elem = encode_elem.find("filters")
|
||||
if filters_elem is not None:
|
||||
for child in filters_elem:
|
||||
if child.text:
|
||||
filters[child.tag] = child.text
|
||||
if child.text and child.text.strip():
|
||||
filters[child.tag] = child.text.strip()
|
||||
|
||||
# --- Audio ---
|
||||
audio = {"stereo": {}, "multi_channel": {}}
|
||||
stereo_elem = root.find("audio/stereo")
|
||||
if stereo_elem is not None:
|
||||
for child in stereo_elem:
|
||||
if child.text:
|
||||
audio["stereo"][child.tag] = int(child.text)
|
||||
if child.text and child.text.strip():
|
||||
audio["stereo"][child.tag] = int(child.text.strip())
|
||||
|
||||
multi_elem = root.find("audio/multi_channel")
|
||||
if multi_elem is not None:
|
||||
for child in multi_elem:
|
||||
if child.text:
|
||||
audio["multi_channel"][child.tag] = int(child.text)
|
||||
if child.text and child.text.strip():
|
||||
audio["multi_channel"][child.tag] = int(child.text.strip())
|
||||
|
||||
# --- Services (Sonarr/Radarr) ---
|
||||
services = {"sonarr": {}, "radarr": {}}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from core.audio_handler import get_audio_streams, choose_audio_bitrate
|
||||
from core.audio_handler import get_audio_streams, choose_audio_bitrate, filter_audio_streams, prompt_user_audio_selection, prompt_for_title_stripping
|
||||
from core.logger_helper import setup_logger
|
||||
|
||||
logger = setup_logger(Path(__file__).parent.parent / "logs")
|
||||
@ -12,7 +12,8 @@ logger = setup_logger(Path(__file__).parent.parent / "logs")
|
||||
|
||||
def run_ffmpeg(input_file: Path, output_file: Path, cq: int, scale_width: int, scale_height: int,
|
||||
src_width: int, src_height: int, filter_flags: str, audio_config: dict,
|
||||
method: str, bitrate_config: dict, subtitle_file: Path = None, audio_language: str = None):
|
||||
method: str, bitrate_config: dict, encoder: str = "nvenc", subtitle_file: Path = None, audio_language: str = None,
|
||||
audio_filter_config: dict = None, test_mode: bool = False):
|
||||
"""
|
||||
Run FFmpeg encode with comprehensive logging.
|
||||
|
||||
@ -20,15 +21,65 @@ def run_ffmpeg(input_file: Path, output_file: Path, cq: int, scale_width: int, s
|
||||
"""
|
||||
streams = get_audio_streams(input_file)
|
||||
|
||||
# Apply audio filter if enabled
|
||||
if audio_filter_config and audio_filter_config.get("enabled", False):
|
||||
# Check if pre-selected streams provided
|
||||
if audio_filter_config.get("preselected"):
|
||||
# Use pre-selected streams (skip interactive)
|
||||
preselected_str = audio_filter_config["preselected"]
|
||||
try:
|
||||
selected_indices = set()
|
||||
for part in preselected_str.split(","):
|
||||
idx = int(part.strip())
|
||||
selected_indices.add(idx)
|
||||
# Filter to only selected streams
|
||||
streams = [s for s in streams if s[0] in selected_indices]
|
||||
# Add strip_title field (False by default for pre-selected)
|
||||
streams = [s + (False,) for s in streams]
|
||||
logger.info(f"Pre-selected audio streams: {[s[0] for s in streams]}")
|
||||
except ValueError:
|
||||
logger.warning(f"Invalid audio_select format: {preselected_str}. Using all streams.")
|
||||
streams = [s + (False,) for s in streams]
|
||||
else:
|
||||
# Check if interactive mode requested (via --filter-audio CLI flag)
|
||||
# If audio_filter_config came from CLI, it has "interactive": True
|
||||
if "interactive" in audio_filter_config and audio_filter_config.get("interactive", False):
|
||||
# Interactive audio selection (show prompt to user)
|
||||
streams = prompt_user_audio_selection(streams)
|
||||
# Prompt for title stripping after stream selection
|
||||
streams = prompt_for_title_stripping(streams)
|
||||
else:
|
||||
# Automatic filtering from config (keep best English + Commentary)
|
||||
streams = filter_audio_streams(input_file, streams)
|
||||
# Add strip_title field (False by default for automatic filtering)
|
||||
streams = [s + (False,) for s in streams]
|
||||
else:
|
||||
# No filtering - add strip_title field as False
|
||||
streams = [s + (False,) for s in streams]
|
||||
|
||||
# Log comprehensive encode settings
|
||||
header = f"\n🧩 ENCODE SETTINGS"
|
||||
logger.info(header)
|
||||
print(" ")
|
||||
|
||||
# Determine encoder display name and settings
|
||||
if encoder == "av1":
|
||||
encoder_name = "AV1 NVENC"
|
||||
encoder_codec = "av1_nvenc"
|
||||
encoder_preset = "p7"
|
||||
encoder_pix_fmt = "yuv420p"
|
||||
encoder_bit_depth = "8-bit"
|
||||
else: # default hevc = HEVC NVENC
|
||||
encoder_name = "HEVC NVENC"
|
||||
encoder_codec = "hevc_nvenc"
|
||||
encoder_preset = "slow"
|
||||
encoder_pix_fmt = "p010le"
|
||||
encoder_bit_depth = "10-bit"
|
||||
|
||||
logger.info(f" Video:")
|
||||
logger.info(f" • Source Resolution: {src_width}x{src_height}")
|
||||
logger.info(f" • Target Resolution: {scale_width}x{scale_height}")
|
||||
logger.info(f" • Encoder: av1_nvenc (preset p1, pix_fmt p010le)")
|
||||
logger.info(f" • Encoder: {encoder_name} (preset {encoder_preset}, {encoder_bit_depth}, pix_fmt {encoder_pix_fmt})")
|
||||
logger.info(f" • Scale Filter: {filter_flags}")
|
||||
logger.info(f" • Encode Method: {method}")
|
||||
if method == "CQ":
|
||||
@ -42,7 +93,7 @@ def run_ffmpeg(input_file: Path, output_file: Path, cq: int, scale_width: int, s
|
||||
logger.info(f" Audio Streams ({len(streams)} detected):")
|
||||
print(" ")
|
||||
|
||||
for (index, channels, avg_bitrate, src_lang, meta_bitrate) in streams:
|
||||
for (index, channels, avg_bitrate, src_lang, meta_bitrate, title, strip_title) in streams:
|
||||
# Normalize to 2ch or 6ch output
|
||||
is_1080_class = scale_height >= 1080 or scale_width >= 1920
|
||||
output_channels = 6 if is_1080_class and channels >= 6 else 2
|
||||
@ -55,7 +106,9 @@ def run_ffmpeg(input_file: Path, output_file: Path, cq: int, scale_width: int, s
|
||||
action = "ENCODE"
|
||||
bitrate_display = f"{br/1000:.0f}kbps"
|
||||
|
||||
line = f" - Stream #{index}: {channels}ch→{output_channels}ch | Lang: {src_lang} | Detected: {avg_bitrate}kbps | Action: {action} | Target: {bitrate_display}"
|
||||
# Include title in display if present
|
||||
title_info = f" | Title: {title}" if title else ""
|
||||
line = f" - Stream #{index}: {channels}ch→{output_channels}ch | Lang: {src_lang} | Detected: {avg_bitrate}kbps | Action: {action} | Target: {bitrate_display}{title_info}"
|
||||
print(line)
|
||||
logger.info(line)
|
||||
|
||||
@ -65,9 +118,17 @@ def run_ffmpeg(input_file: Path, output_file: Path, cq: int, scale_width: int, s
|
||||
if subtitle_file:
|
||||
cmd.extend(["-i", str(subtitle_file)])
|
||||
|
||||
# In test mode, only encode first 15 minutes
|
||||
if test_mode:
|
||||
cmd.extend(["-t", "900"]) # 900 seconds = 15 minutes
|
||||
|
||||
cmd.extend([
|
||||
"-vf",f"scale={scale_width}:{scale_height}:flags={filter_flags}:force_original_aspect_ratio=decrease",
|
||||
"-map","0:v","-map","0:a"])
|
||||
"-map","0:v"])
|
||||
|
||||
# Map only selected audio streams
|
||||
for index, _, _, _, _, _, _ in streams:
|
||||
cmd.extend(["-map", f"0:{index}"])
|
||||
|
||||
# Add subtitle mapping if present
|
||||
if subtitle_file:
|
||||
@ -76,7 +137,7 @@ def run_ffmpeg(input_file: Path, output_file: Path, cq: int, scale_width: int, s
|
||||
cmd.extend(["-map", "0:s?"])
|
||||
|
||||
cmd.extend([
|
||||
"-c:v","av1_nvenc","-preset","p1","-pix_fmt","p010le"])
|
||||
"-c:v", encoder_codec, "-preset", encoder_preset, "-pix_fmt", encoder_pix_fmt])
|
||||
|
||||
if method=="CQ":
|
||||
cmd += ["-cq", str(cq)]
|
||||
@ -88,7 +149,7 @@ def run_ffmpeg(input_file: Path, output_file: Path, cq: int, scale_width: int, s
|
||||
bufsize = bitrate_config.get(f"bufsize_{res_key}", "1800k")
|
||||
cmd += ["-b:v", vb, "-maxrate", maxrate, "-bufsize", bufsize]
|
||||
|
||||
for i, (index, channels, avg_bitrate, src_lang, meta_bitrate) in enumerate(streams):
|
||||
for i, (index, channels, avg_bitrate, src_lang, meta_bitrate, title, strip_title) in enumerate(streams):
|
||||
# Normalize to 2ch or 6ch output
|
||||
is_1080_class = scale_height >= 1080 or scale_width >= 1920
|
||||
output_channels = 6 if is_1080_class and channels >= 6 else 2
|
||||
@ -100,6 +161,9 @@ def run_ffmpeg(input_file: Path, output_file: Path, cq: int, scale_width: int, s
|
||||
# Only add language metadata if explicitly provided
|
||||
if audio_language:
|
||||
cmd += [f"-metadata:s:a:{i}", f"language={audio_language}"]
|
||||
# Strip title metadata if requested
|
||||
if strip_title:
|
||||
cmd += [f"-metadata:s:a:{i}", "title="]
|
||||
else:
|
||||
# Re-encode with target bitrate
|
||||
# EAC3 for multichannel, AAC for stereo
|
||||
@ -122,7 +186,9 @@ def run_ffmpeg(input_file: Path, output_file: Path, cq: int, scale_width: int, s
|
||||
# Only add language metadata if explicitly provided
|
||||
if audio_language:
|
||||
cmd += [f"-metadata:s:a:{i}", f"language={audio_language}"]
|
||||
|
||||
# Strip title metadata if requested
|
||||
if strip_title:
|
||||
cmd += [f"-metadata:s:a:{i}", "title="]
|
||||
# Add subtitle codec and metadata if subtitles are present
|
||||
if subtitle_file:
|
||||
cmd += ["-c:s", "srt", "-metadata:s:s:0", "language=eng"]
|
||||
|
||||
@ -11,7 +11,7 @@ from pathlib import Path
|
||||
from core.audio_handler import get_audio_streams
|
||||
from core.encode_engine import run_ffmpeg
|
||||
from core.logger_helper import setup_logger, setup_failure_logger
|
||||
from core.video_handler import get_source_resolution, determine_target_resolution
|
||||
from core.video_handler import get_source_resolution, get_source_bit_depth, determine_target_resolution
|
||||
|
||||
logger = setup_logger(Path(__file__).parent.parent / "logs")
|
||||
failure_logger = setup_failure_logger(Path(__file__).parent.parent / "logs")
|
||||
@ -34,7 +34,7 @@ def _cleanup_temp_files(temp_input: Path, temp_output: Path):
|
||||
logger.warning(f"Could not delete temp output {temp_output.name}: {e}")
|
||||
|
||||
|
||||
def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str, config: dict, tracker_file: Path, test_mode: bool = False, audio_language: str = None):
|
||||
def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str, config: dict, tracker_file: Path, test_mode: bool = False, audio_language: str = None, filter_audio: bool = None, audio_select: str = None, encoder: str = "hevc"):
|
||||
"""
|
||||
Process all video files in folder with appropriate encoding settings.
|
||||
|
||||
@ -47,6 +47,9 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
|
||||
tracker_file: Path to CSV tracker file
|
||||
test_mode: If True, only encode first file and skip final move/cleanup
|
||||
audio_language: Optional language code to tag audio (e.g., 'eng', 'spa'). If None, no tagging applied.
|
||||
filter_audio: If True, show interactive audio selection prompt. If None, use config setting.
|
||||
audio_select: Pre-selected audio streams (comma-separated, e.g., "1,2"). Skips interactive prompt.
|
||||
encoder: Video encoder to use - "hevc" for HEVC NVENC 10-bit (default) or "av1" for AV1 NVENC 8-bit.
|
||||
"""
|
||||
if not folder.exists():
|
||||
print(f"❌ Folder not found: {folder}")
|
||||
@ -67,30 +70,33 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
|
||||
filter_flags = filters_config.get("default","lanczos")
|
||||
folder_lower = str(folder).lower()
|
||||
is_tv = "\\tv\\" in folder_lower or "/tv/" in folder_lower
|
||||
is_anime = "\\anime\\" in folder_lower or "/anime/" in folder_lower
|
||||
if is_tv:
|
||||
filter_flags = filters_config.get("tv","bicubic")
|
||||
elif is_anime:
|
||||
filter_flags = filters_config.get("anime", filters_config.get("default","lanczos"))
|
||||
|
||||
processing_folder = Path(config["processing_folder"])
|
||||
processing_folder.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Determine if we're in smart mode (no explicit mode specified)
|
||||
is_smart_mode = transcode_mode not in ["cq", "bitrate"] # Default/smart mode
|
||||
# Determine encoding mode
|
||||
is_smart_mode = transcode_mode == "compression" # Try CQ first, then bitrate fallback
|
||||
is_forced_cq = transcode_mode == "cq"
|
||||
is_forced_bitrate = transcode_mode == "bitrate"
|
||||
|
||||
# Track files for potential retry in smart mode
|
||||
failed_cq_files = [] # List of (file_path, metadata) for CQ failures in smart mode
|
||||
failed_cq_files = [] # List of (file_path, metadata) for CQ failures in compression mode
|
||||
consecutive_failures = 0
|
||||
max_consecutive = 3
|
||||
|
||||
# Phase 1: Process files with initial mode strategy
|
||||
print(f"\n{'='*60}")
|
||||
if is_smart_mode:
|
||||
print("📋 MODE: Smart (Try CQ first, retry with Bitrate if needed)")
|
||||
print("📋 MODE: Compression (Try CQ first, retry with Bitrate if needed)")
|
||||
elif is_forced_cq:
|
||||
print("📋 MODE: Forced CQ (skip failures, log them)")
|
||||
print("📋 MODE: CQ (constant quality, skip failures, log them)")
|
||||
else:
|
||||
print("📋 MODE: Forced Bitrate (skip failures, log them)")
|
||||
print("📋 MODE: Bitrate (bitrate mode only, skip failures, log them)")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
skipped_count = 0
|
||||
@ -111,10 +117,28 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
|
||||
print(f"📁 Processing: {file.name}")
|
||||
|
||||
temp_input = (processing_folder / file.name).resolve()
|
||||
shutil.copy2(file, temp_input)
|
||||
logger.info(f"Copied {file.name} → {temp_input.name}")
|
||||
|
||||
# Verify file was copied and is accessible
|
||||
# Check if file already exists in processing folder
|
||||
if temp_input.exists() and os.access(temp_input, os.R_OK):
|
||||
source_size = file.stat().st_size
|
||||
temp_size = temp_input.stat().st_size
|
||||
|
||||
# Verify it's complete (same size as source)
|
||||
if source_size == temp_size:
|
||||
print(f"✓ Found existing copy in processing folder (verified complete)")
|
||||
logger.info(f"File already in processing: {file.name} ({temp_size/1e6:.2f} MB verified complete)")
|
||||
else:
|
||||
# File exists but incomplete - recopy
|
||||
print(f"⚠️ Existing copy incomplete ({temp_size/1e6:.2f} MB vs {source_size/1e6:.2f} MB source). Re-copying...")
|
||||
logger.warning(f"Incomplete copy detected for {file.name}. Re-copying.")
|
||||
shutil.copy2(file, temp_input)
|
||||
logger.info(f"Re-copied {file.name} → {temp_input.name}")
|
||||
else:
|
||||
# File doesn't exist or not accessible - copy it
|
||||
shutil.copy2(file, temp_input)
|
||||
logger.info(f"Copied {file.name} → {temp_input.name}")
|
||||
|
||||
# Verify file is accessible
|
||||
for attempt in range(3):
|
||||
if temp_input.exists() and os.access(temp_input, os.R_OK):
|
||||
break
|
||||
@ -154,6 +178,29 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
|
||||
src_width, src_height, explicit_resolution
|
||||
)
|
||||
|
||||
# Auto-select encoder based on source bit depth if not explicitly specified
|
||||
# (explicit encoder arg is passed in, so if user didn't specify, it's still the default)
|
||||
# We need to check if encoder came from CLI or is the default
|
||||
# For now, we'll always auto-detect and only skip if encoder was explicitly set
|
||||
# Since we can't distinguish in the current flow, we'll add a parameter to track this
|
||||
selected_encoder = encoder # Start with what was passed (may be default)
|
||||
|
||||
# Check source bit depth for auto-selection
|
||||
source_bit_depth = get_source_bit_depth(temp_input)
|
||||
|
||||
# Auto-select encoder based on source bit depth
|
||||
# 10-bit or higher (including 12-bit) → HEVC (supports up to 10-bit)
|
||||
# 8-bit → AV1 (more efficient for 8-bit)
|
||||
if source_bit_depth >= 10:
|
||||
selected_encoder = "hevc"
|
||||
encoder_note = "auto-selected (10+ bit source)"
|
||||
else:
|
||||
selected_encoder = "av1"
|
||||
encoder_note = "auto-selected (8-bit source)"
|
||||
|
||||
print(f"ℹ️ Encoder: {selected_encoder} ({encoder_note})")
|
||||
logger.info(f"Selected encoder: {selected_encoder} - Source bit depth: {source_bit_depth}-bit")
|
||||
|
||||
# Log resolution decision
|
||||
if explicit_resolution:
|
||||
logger.info(f"Using explicitly specified resolution: {res_width}x{res_height}")
|
||||
@ -168,8 +215,16 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
|
||||
print(f"ℹ️ Source {src_width}x{src_height} is at or below 1080p. Preserving resolution.")
|
||||
logger.info(f"Source {src_width}x{src_height} (<=1080p). Preserving source resolution.")
|
||||
|
||||
# Set CQ based on content type and target resolution
|
||||
content_cq = config["encode"]["cq"].get(f"tv_{target_resolution}" if is_tv else f"movie_{target_resolution}", 32)
|
||||
# Set CQ based on content type, target resolution, and encoder
|
||||
if is_anime:
|
||||
cq_key = f"anime_{target_resolution}"
|
||||
elif is_tv:
|
||||
cq_key = f"tv_{target_resolution}"
|
||||
else:
|
||||
cq_key = f"movie_{target_resolution}"
|
||||
# Look up CQ from encoder-specific section
|
||||
encoder_cq_config = config["encode"]["cq"].get(selected_encoder, {})
|
||||
content_cq = encoder_cq_config.get(cq_key, 32)
|
||||
file_cq = cq if cq is not None else content_cq
|
||||
|
||||
# Always output as .mkv (AV1 video codec) with [EHX] suffix
|
||||
@ -185,9 +240,21 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
|
||||
|
||||
# Attempt encoding
|
||||
try:
|
||||
# Determine audio_filter config (CLI arg overrides config file)
|
||||
# --filter-audio flag means: show interactive prompt
|
||||
if filter_audio:
|
||||
audio_filter_config = {"enabled": True, "interactive": True}
|
||||
# If --audio-select provided, skip interactive and use pre-selected streams
|
||||
if audio_select:
|
||||
audio_filter_config["preselected"] = audio_select
|
||||
else:
|
||||
# Use config file setting (if present)
|
||||
audio_filter_config = config.get("general", {}).get("audio_filter", {})
|
||||
|
||||
orig_size, out_size, reduction_ratio = run_ffmpeg(
|
||||
temp_input, temp_output, file_cq, res_width, res_height, src_width, src_height,
|
||||
filter_flags, audio_config, method, bitrate_config, subtitle_file, audio_language
|
||||
filter_flags, audio_config, method, bitrate_config, selected_encoder, subtitle_file, audio_language,
|
||||
audio_filter_config, test_mode
|
||||
)
|
||||
|
||||
# Check if encode met size target
|
||||
@ -283,7 +350,7 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
|
||||
_save_successful_encoding(
|
||||
file, temp_input, temp_output, orig_size, out_size,
|
||||
reduction_ratio, method, src_width, src_height, res_width, res_height,
|
||||
file_cq, tracker_file, folder, is_tv, config, test_mode, subtitle_file
|
||||
file_cq, tracker_file, folder, is_tv, suffix, config, test_mode, subtitle_file
|
||||
)
|
||||
|
||||
# In test mode, stop after first successful file
|
||||
@ -335,8 +402,8 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
|
||||
temp_input, temp_output, file_data['file_cq'],
|
||||
file_data['res_width'], file_data['res_height'],
|
||||
file_data['src_width'], file_data['src_height'],
|
||||
filter_flags, audio_config, "Bitrate", bitrate_config,
|
||||
file_data.get('subtitle_file'), audio_language
|
||||
filter_flags, audio_config, "Bitrate", bitrate_config, selected_encoder,
|
||||
file_data.get('subtitle_file'), audio_language, None, test_mode
|
||||
)
|
||||
|
||||
# Check if bitrate also failed
|
||||
@ -358,7 +425,7 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
|
||||
file_data['src_width'], file_data['src_height'],
|
||||
file_data['res_width'], file_data['res_height'],
|
||||
file_data['file_cq'], tracker_file,
|
||||
folder, file_data['is_tv'], config, False,
|
||||
folder, file_data['is_tv'], suffix, config, False,
|
||||
file_data.get('subtitle_file')
|
||||
)
|
||||
|
||||
@ -390,7 +457,7 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
|
||||
|
||||
def _save_successful_encoding(file, temp_input, temp_output, orig_size, out_size,
|
||||
reduction_ratio, method, src_width, src_height, res_width, res_height,
|
||||
file_cq, tracker_file, folder, is_tv, config=None, test_mode=False, subtitle_file=None):
|
||||
file_cq, tracker_file, folder, is_tv, suffix, config=None, test_mode=False, subtitle_file=None):
|
||||
"""Helper function to save successfully encoded files with [EHX] tag and clean up subtitle files."""
|
||||
|
||||
# In test mode, show ratio and skip file move/cleanup
|
||||
@ -411,13 +478,24 @@ def _save_successful_encoding(file, temp_input, temp_output, orig_size, out_size
|
||||
logger.info(f"TEST MODE - File: {file.name} | Ratio: {percentage}% | Method: {method}")
|
||||
return
|
||||
|
||||
dest_file = file.parent / temp_output.name
|
||||
# Check if file is in a Featurettes folder - if so, remove suffix from destination filename
|
||||
folder_parts = [p.lower() for p in file.parent.parts]
|
||||
is_featurette = "featurettes" in folder_parts
|
||||
|
||||
if is_featurette:
|
||||
# Remove suffix from temp_output.name for Featurettes
|
||||
output_name = temp_output.name
|
||||
if suffix in output_name:
|
||||
output_name = output_name.replace(suffix, "")
|
||||
dest_file = file.parent / output_name
|
||||
else:
|
||||
dest_file = file.parent / temp_output.name
|
||||
|
||||
shutil.move(temp_output, dest_file)
|
||||
print(f"🚚 Moved {temp_output.name} → {dest_file.name}")
|
||||
logger.info(f"Moved {temp_output.name} → {dest_file.name}")
|
||||
|
||||
# Classify file type based on folder
|
||||
folder_parts = [p.lower() for p in folder.parts]
|
||||
# Classify file type based on folder (folder_parts already defined earlier)
|
||||
if "tv" in folder_parts:
|
||||
f_type = "tv"
|
||||
tv_index = folder_parts.index("tv")
|
||||
@ -462,8 +540,13 @@ def _save_successful_encoding(file, temp_input, temp_output, orig_size, out_size
|
||||
|
||||
try:
|
||||
temp_input.unlink()
|
||||
file.unlink()
|
||||
logger.info(f"Deleted original and processing copy for {file.name}")
|
||||
|
||||
# Only delete original file if NOT in Featurettes folder (Featurettes are re-encoded in place)
|
||||
if not is_featurette:
|
||||
file.unlink()
|
||||
logger.info(f"Deleted original and processing copy for {file.name}")
|
||||
else:
|
||||
logger.info(f"Featurettes file preserved at origin: {file.name}")
|
||||
|
||||
# Clean up subtitle file if it was embedded
|
||||
if subtitle_file and subtitle_file.exists():
|
||||
|
||||
@ -22,17 +22,56 @@ def get_source_resolution(input_file: Path) -> tuple:
|
||||
"-of", "default=noprint_wrappers=1:nokey=1:noprint_wrappers=1",
|
||||
str(input_file)
|
||||
]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
lines = result.stdout.strip().split("\n")
|
||||
width = int(lines[0]) if len(lines) > 0 else 1920
|
||||
height = int(lines[1]) if len(lines) > 1 else 1080
|
||||
logger.info(f"Source resolution detected: {width}x{height}")
|
||||
return (width, height)
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='ignore', check=False)
|
||||
if result.stdout:
|
||||
lines = result.stdout.strip().split("\n")
|
||||
width = int(lines[0]) if len(lines) > 0 and lines[0].strip() else 1920
|
||||
height = int(lines[1]) if len(lines) > 1 and lines[1].strip() else 1080
|
||||
logger.info(f"Source resolution detected: {width}x{height}")
|
||||
return (width, height)
|
||||
else:
|
||||
logger.warning(f"ffprobe returned no output for {input_file.name}. Defaulting to 1920x1080")
|
||||
return (1920, 1080)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to detect source resolution: {e}. Defaulting to 1920x1080")
|
||||
return (1920, 1080)
|
||||
|
||||
|
||||
def get_source_bit_depth(input_file: Path) -> int:
|
||||
"""
|
||||
Detect source video bit depth (8, 10, or 12).
|
||||
Returns: 12, 10, or 8 (default)
|
||||
"""
|
||||
try:
|
||||
cmd = [
|
||||
"ffprobe", "-v", "error",
|
||||
"-select_streams", "v:0",
|
||||
"-show_entries", "stream=pix_fmt",
|
||||
"-of", "default=noprint_wrappers=1:nokey=1",
|
||||
str(input_file)
|
||||
]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='ignore', check=False)
|
||||
if result.stdout:
|
||||
pix_fmt = result.stdout.strip().lower()
|
||||
# Check for 12-bit indicators first
|
||||
if any(x in pix_fmt for x in ["12le", "12be"]):
|
||||
logger.info(f"Source bit depth detected: 12-bit ({pix_fmt})")
|
||||
return 12
|
||||
# Check for 10-bit indicators
|
||||
elif any(x in pix_fmt for x in ["10le", "10be", "p010", "yuv420p10"]):
|
||||
logger.info(f"Source bit depth detected: 10-bit ({pix_fmt})")
|
||||
return 10
|
||||
else:
|
||||
logger.info(f"Source bit depth detected: 8-bit ({pix_fmt})")
|
||||
return 8
|
||||
else:
|
||||
logger.debug(f"Could not detect bit depth for {input_file.name}. Defaulting to 8-bit")
|
||||
return 8
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to detect source bit depth: {e}. Defaulting to 8-bit")
|
||||
return 8
|
||||
|
||||
|
||||
def determine_target_resolution(src_width: int, src_height: int, explicit_resolution: str = None) -> tuple:
|
||||
"""
|
||||
Determine target resolution based on source and explicit override.
|
||||
|
||||
4212
logs/conversion.log
4212
logs/conversion.log
File diff suppressed because it is too large
Load Diff
@ -31,3 +31,30 @@
|
||||
2026-01-01 01:25:05 | Supernatural - S07E17 - The Born-Again Identity x265 AC3 Bluray-1080p HiQVE.mkv | CQ error: Command '['ffmpeg', '-y', '-i', 'C:\\Users\\Tyler\\Documents\\GitHub\\conversion_project\\processing
|
||||
2026-01-01 13:17:15 | The Office (US) - S08E04 - Garden Party x265 AAC Bluray-1080p Silence.mkv | CQ failed: Size threshold not met (122.2%)
|
||||
2026-01-01 13:22:48 | The Office (US) - S08E04 - Garden Party x265 AAC Bluray-1080p Silence.mkv | CQ failed: Size threshold not met (101.3%)
|
||||
2026-01-01 20:53:58 | ._Superman (2025) x265 EAC3 7.1 Bluray-1080p Ghost.mkv | CQ error: Command '['ffmpeg', '-y', '-i', 'C:\\Users\\Tyler\\Documents\\GitHub\\conversion_project\\processing
|
||||
2026-01-01 20:55:56 | [sam] Vanitas no Carte - 02 [BD 1080p FLAC] [8822B4BC].mkv | Unexpected error: too many values to unpack (expected 5)
|
||||
2026-01-01 20:56:12 | [sam] Vanitas no Carte - 03 [BD 1080p FLAC] [BDE63D2B].mkv | Unexpected error: too many values to unpack (expected 5)
|
||||
2026-01-01 21:00:10 | [sam] Vanitas no Carte - 02 [BD 1080p FLAC] [8822B4BC].mkv | Unexpected error: too many values to unpack (expected 5)
|
||||
2026-01-01 22:51:03 | ._Superman (2025) x265 EAC3 7.1 Bluray-1080p Ghost.mkv | CQ error: Command '['ffmpeg', '-y', '-i', 'C:\\Users\\Tyler\\Documents\\GitHub\\conversion_project\\processing
|
||||
2026-01-01 22:51:21 | A New Era꞉ DC Takes Off.mkv | CQ failed: Size threshold not met (81.8%)
|
||||
2026-01-01 22:51:37 | Adventures in the Making of “Superman”.mkv | Unexpected error: 'NoneType' object has no attribute 'split'
|
||||
2026-01-01 22:53:54 | ._Superman (2025) x265 EAC3 7.1 Bluray-1080p Ghost.mkv | CQ error: Command '['ffmpeg', '-y', '-i', 'C:\\Users\\Tyler\\Documents\\GitHub\\conversion_project\\processing
|
||||
2026-01-01 22:54:13 | A New Era꞉ DC Takes Off.mkv | CQ failed: Size threshold not met (81.8%)
|
||||
2026-01-01 22:57:40 | ._Superman (2025) x265 EAC3 7.1 Bluray-1080p Ghost.mkv | CQ error: Command '['ffmpeg', '-y', '-i', 'C:\\Users\\Tyler\\Documents\\GitHub\\conversion_project\\processing
|
||||
2026-01-01 22:58:01 | A New Era꞉ DC Takes Off.mkv | Unexpected error: name 'suffix' is not defined
|
||||
2026-01-08 10:37:48 | According to Plan.mkv | CQ failed: Size threshold not met (94.3%)
|
||||
2026-01-08 10:37:56 | Bloopers of the Caribbean.mkv | CQ failed: Size threshold not met (122.9%)
|
||||
2026-01-08 10:39:01 | Captain Jack - From Head to Toe.mkv | CQ failed: Size threshold not met (110.9%)
|
||||
2026-01-08 11:46:19 | Anatomy of a Scene - The Maelstrom.mkv | CQ failed: Size threshold not met (202.4%)
|
||||
2026-01-08 11:46:59 | Bloopers of the Caribbean.mkv | CQ failed: Size threshold not met (107.0%)
|
||||
2026-01-08 11:50:26 | Deleted & Extended Scenes.mkv | CQ failed: Size threshold not met (116.3%)
|
||||
2026-01-08 13:38:12 | An Epic at Sea.mkv | CQ failed: Size threshold not met (85.1%)
|
||||
2026-01-08 13:38:27 | Becoming Barbossa.mkv | CQ failed: Size threshold not met (93.0%)
|
||||
2026-01-08 13:38:47 | Becoming Captain Jack.mkv | CQ failed: Size threshold not met (95.2%)
|
||||
2026-01-08 13:56:34 | Bloopers of the Caribbean.mkv | CQ failed: Size threshold not met (103.5%)
|
||||
2026-01-08 14:04:01 | Dead Men Tell No Tales - The Making of a New Adventure.mkv | CQ failed: Size threshold not met (156.6%)
|
||||
2026-01-08 14:22:53 | Bloopers of the Caribbean.mkv | CQ failed: Size threshold not met (116.2%)
|
||||
2026-01-08 14:24:16 | Deleted and Extended Scenes.mkv | CQ failed: Size threshold not met (115.3%)
|
||||
2026-01-08 14:26:55 | Easter Eggs.mkv | CQ failed: Size threshold not met (227.9%)
|
||||
2026-01-08 16:15:19 | Trailer [kr].mkv | CQ failed: Size threshold not met (106.3%)
|
||||
2026-01-08 18:49:04 | The Ultimate Villain.mkv | CQ failed: Size threshold not met (85.9%)
|
||||
|
||||
23
main.py
23
main.py
@ -89,10 +89,10 @@ def main():
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
%(prog)s "C:\\Videos\\Movies" # Smart mode (preserve resolution, 4K->1080p)
|
||||
%(prog)s "C:\\Videos\\Movies" # CQ mode (preserve resolution, 4K->1080p)
|
||||
%(prog)s "C:\\Videos\\TV" --r 720 --m bitrate # Force 720p, bitrate mode
|
||||
%(prog)s "C:\\Videos\\Anime" --cq 28 --r 1080 # Force 1080p, CQ=28
|
||||
%(prog)s "C:\\Videos\\Low-Res" --r 480 # Force 480p for low-res content
|
||||
%(prog)s "C:\\Videos\\Low-Res" --m compression --r 480 # Force 480p, try CQ then bitrate for compression
|
||||
"""
|
||||
)
|
||||
|
||||
@ -100,8 +100,13 @@ Examples:
|
||||
parser.add_argument("--cq", type=int, help="Override default CQ value")
|
||||
parser.add_argument(
|
||||
"--m", "--mode", dest="transcode_mode", default="cq",
|
||||
choices=["cq", "bitrate"],
|
||||
help="Encode mode: CQ (constant quality) or Bitrate mode"
|
||||
choices=["cq", "bitrate", "compression"],
|
||||
help="Encode mode: 'cq' (constant quality only), 'bitrate' (bitrate only), or 'compression' (try CQ then bitrate fallback)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--encoder", dest="encoder", default="hevc",
|
||||
choices=["hevc", "av1"],
|
||||
help="Video encoder: 'hevc' for HEVC NVENC 10-bit (default), 'av1' for AV1 NVENC 8-bit"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--r", "--resolution", dest="resolution", default=None,
|
||||
@ -116,6 +121,14 @@ Examples:
|
||||
"--language", dest="audio_language", default=None,
|
||||
help="Tag audio streams with language code (e.g., eng, spa, fra). If not set, audio language is unchanged"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--filter-audio", dest="filter_audio", default=None, action="store_true",
|
||||
help="Interactive audio selection: show audio streams and let user choose which to keep (overrides config setting)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--audio-select", dest="audio_select", default=None,
|
||||
help="Pre-select audio streams to keep (comma-separated, e.g., 1,2). Skips interactive prompt. Requires --filter-audio"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Load configuration
|
||||
@ -132,7 +145,7 @@ Examples:
|
||||
return
|
||||
|
||||
# Process folder
|
||||
process_folder(folder, args.cq, args.transcode_mode, args.resolution, config, TRACKER_FILE, args.test_mode, args.audio_language)
|
||||
process_folder(folder, args.cq, args.transcode_mode, args.resolution, config, TRACKER_FILE, args.test_mode, args.audio_language, args.filter_audio, args.audio_select, args.encoder)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
322
path_manager/.cache/.cache_tv.json
Normal file
322
path_manager/.cache/.cache_tv.json
Normal file
@ -0,0 +1,322 @@
|
||||
{
|
||||
"P:\\tv\\Hero Inside (2023)": 7372329680,
|
||||
"P:\\tv\\Below Deck": 47516712212,
|
||||
"P:\\tv\\The Penguin": 4459075060,
|
||||
"P:\\tv\\Banshee (2013)": 25030541772,
|
||||
"P:\\tv\\Dungeons & Dragons": 6660128393,
|
||||
"P:\\tv\\Made For Love (2021)": 2211136772,
|
||||
"P:\\tv\\Sirens (2025)": 4246622090,
|
||||
"P:\\tv\\Landman (2024)": 35220290035,
|
||||
"P:\\tv\\Last Man Standing": 49393251846,
|
||||
"P:\\tv\\Alien - Earth (2025)": 2926145405,
|
||||
"P:\\tv\\The Big Door Prize": 2314902686,
|
||||
"P:\\tv\\Government Cheese (2025)": 15970704500,
|
||||
"P:\\tv\\In the Dark (2019)": 2555891397,
|
||||
"P:\\tv\\Tulsa King": 41351406080,
|
||||
"P:\\tv\\Dopesick": 2571994785,
|
||||
"P:\\tv\\Taylor (2025)": 2621206209,
|
||||
"P:\\tv\\Star Trek Lower Decks": 33090597113,
|
||||
"P:\\tv\\Face Off (2011)": 83155672195,
|
||||
"P:\\tv\\Catch-22": 7113496871,
|
||||
"P:\\tv\\Canada's Drag Race": 103586850759,
|
||||
"P:\\tv\\Over the Garden Wall": 2937573633,
|
||||
"P:\\tv\\The Traitors (US) (2023)": 48149750078,
|
||||
"P:\\tv\\1923": 22125507023,
|
||||
"P:\\tv\\Loki": 20082144632,
|
||||
"P:\\tv\\House of the Dragon": 23959073249,
|
||||
"P:\\tv\\The Trunk (2024)": 16810949304,
|
||||
"P:\\tv\\The Chosen (2019)": 54241850899,
|
||||
"P:\\tv\\Lucky Hank": 7336222432,
|
||||
"P:\\tv\\Station Eleven": 2708694925,
|
||||
"P:\\tv\\Kitchen Nightmares UK": 11563663098,
|
||||
"P:\\tv\\The Good Lord Bird (2020)": 5619421375,
|
||||
"P:\\tv\\Wolf Pack": 6844099384,
|
||||
"P:\\tv\\Below Deck Mediterranean": 39902249615,
|
||||
"P:\\tv\\The Old Man (2022)": 26139845941,
|
||||
"P:\\tv\\Schitt's Creek": 9325109901,
|
||||
"P:\\tv\\Mr. & Mrs. Smith (2024)": 5316681916,
|
||||
"P:\\tv\\Percy Jackson and the Olympians": 3558450335,
|
||||
"P:\\tv\\Firefly (2002)": 7517428895,
|
||||
"P:\\tv\\Ballers": 13002096756,
|
||||
"P:\\tv\\Bupkis": 13034439710,
|
||||
"P:\\tv\\The Offer": 9070667475,
|
||||
"P:\\tv\\Life After People (2009)": 45628647899,
|
||||
"P:\\tv\\The Lord of the Rings - The Rings of Power": 12834498889,
|
||||
"P:\\tv\\Paradise (2025)": 8024209737,
|
||||
"P:\\tv\\Nobody Wants This": 11516933757,
|
||||
"P:\\tv\\Shrinking (2023)": 17293593983,
|
||||
"P:\\tv\\Hawkeye": 13524278345,
|
||||
"P:\\tv\\Home Economics": 14315967074,
|
||||
"P:\\tv\\Time Bandits (2024)": 6997478287,
|
||||
"P:\\tv\\Lessons in Chemistry (2023)": 5485801173,
|
||||
"P:\\tv\\1883": 4514294832,
|
||||
"P:\\tv\\Love, Death & Robots (2019)": 8204860116,
|
||||
"P:\\tv\\The Legend of Vox Machina": 25197294503,
|
||||
"P:\\tv\\Harry Potter - Wizards of Baking (2024)": 23545641052,
|
||||
"P:\\tv\\The Bachelor": 40368931577,
|
||||
"P:\\tv\\American Horror Story": 142468660014,
|
||||
"P:\\tv\\Yellowstone (2018)": 89724605866,
|
||||
"P:\\tv\\St. Denis Medical (2024)": 18704263469,
|
||||
"P:\\tv\\Cobra Kai": 39761471967,
|
||||
"P:\\tv\\Power (2014)": 20414619656,
|
||||
"P:\\tv\\The Originals (2013)": 72912846985,
|
||||
"P:\\tv\\The Edge of Sleep": 1358235145,
|
||||
"P:\\tv\\3 Body Problem": 11369334730,
|
||||
"P:\\tv\\New Girl": 40676856398,
|
||||
"P:\\tv\\Assembly Required (2021)": 5737519036,
|
||||
"P:\\tv\\30 Rock (2006)": 81412969909,
|
||||
"P:\\tv\\Rupauls Drag Race UK vs The World": 35504142221,
|
||||
"P:\\tv\\Daredevil - Born Again (2025)": 7647367391,
|
||||
"P:\\tv\\Brooklyn Nine Nine": 45722673163,
|
||||
"P:\\tv\\Taskmaster - Champion of Champions": 2700754514,
|
||||
"P:\\tv\\Kim's Convenience": 30475634673,
|
||||
"P:\\tv\\The Office (US)": 161867626607,
|
||||
"P:\\tv\\Stranger Things (2016)": 66712664909,
|
||||
"P:\\tv\\Rupaul's Drag Race Vegas Revue": 2532474468,
|
||||
"P:\\tv\\The Umbrella Academy": 55348092191,
|
||||
"P:\\tv\\Secret Celebrity RuPaul's Drag Race": 4211193920,
|
||||
"P:\\tv\\Andor (2022)": 25679584728,
|
||||
"P:\\tv\\The Bondsman (2025)": 3112664353,
|
||||
"P:\\tv\\Ghosts (2021)": 4574333812,
|
||||
"P:\\tv\\Interior Chinatown": 3167640001,
|
||||
"P:\\tv\\Selfie": 5013734266,
|
||||
"P:\\tv\\Supernatural": 209274293691,
|
||||
"P:\\tv\\Superman and Lois": 44881535930,
|
||||
"P:\\tv\\Black Sails (2014)": 11356486450,
|
||||
"P:\\tv\\Taskmaster (CA) (2022)": 2431664380,
|
||||
"P:\\tv\\The Last of Us": 30545352719,
|
||||
"P:\\tv\\Halo": 6961206915,
|
||||
"P:\\tv\\Home Improvement 1991": 48878774505,
|
||||
"P:\\tv\\Detroiters (2017)": 33750584701,
|
||||
"P:\\tv\\Wildemount Wildlings (2025)": 3348907992,
|
||||
"P:\\tv\\Terminator Zero": 3384699699,
|
||||
"P:\\tv\\Um, Actually": 12360993522,
|
||||
"P:\\tv\\The Rain (2018)": 2941174698,
|
||||
"P:\\tv\\Harley Quinn": 20857796821,
|
||||
"P:\\tv\\Lawmen - Bass Reeves (2023)": 5363156538,
|
||||
"P:\\tv\\Parks and Recreation": 37277190974,
|
||||
"P:\\tv\\Mythic Quest": 16965795814,
|
||||
"P:\\tv\\Invincible (2021)": 19742824176,
|
||||
"P:\\tv\\The Bear (2022)": 43665628138,
|
||||
"P:\\tv\\Jentry Chau vs. the Underworld (2024)": 1406237358,
|
||||
"P:\\tv\\Countdown (2025)": 8935252687,
|
||||
"P:\\tv\\The Great British Bake Off": 78,
|
||||
"P:\\tv\\Smartypants": 15959708127,
|
||||
"P:\\tv\\Scenes from a Marriage (US)": 12493986505,
|
||||
"P:\\tv\\The Franchise (2024)": 2981270395,
|
||||
"P:\\tv\\Chad Powers (2025)": 2474659236,
|
||||
"P:\\tv\\Doctor Who (2005)": 5820708419,
|
||||
"P:\\tv\\Bad Monkey": 7767595411,
|
||||
"P:\\tv\\Swimming with Sharks": 4426141798,
|
||||
"P:\\tv\\English Teacher": 7603165476,
|
||||
"P:\\tv\\Resident Alien (2021)": 17522605407,
|
||||
"P:\\tv\\Krypton (2018)": 10875524680,
|
||||
"P:\\tv\\Vikings (2013)": 194095449878,
|
||||
"P:\\tv\\Arcane (2021)": 19588567847,
|
||||
"P:\\tv\\Ludwig (2024)": 2670615425,
|
||||
"P:\\tv\\Canada's Drag Race vs The World": 7844155647,
|
||||
"P:\\tv\\BattleBots (2015)": 69,
|
||||
"P:\\tv\\Abbott Elementary (2021)": 24595462535,
|
||||
"P:\\tv\\Billy the Kid": 44803721006,
|
||||
"P:\\tv\\Quantum Leap 2022": 8902776416,
|
||||
"P:\\tv\\Obi-Wan Kenobi": 13867986923,
|
||||
"P:\\tv\\Matlock (2024)": 34470939613,
|
||||
"P:\\tv\\The Fall of Diddy (2025)": 2431035593,
|
||||
"P:\\tv\\Kaos": 5164057710,
|
||||
"P:\\tv\\Shifting Gears (2025)": 12649531141,
|
||||
"P:\\tv\\Saving Hope": 33116225358,
|
||||
"P:\\tv\\Gen V (2023)": 16871757804,
|
||||
"P:\\tv\\Below Deck Sailing Yacht": 12706704039,
|
||||
"P:\\tv\\Monarch Legacy of Monsters": 18371826949,
|
||||
"P:\\tv\\High Potential": 24339309461,
|
||||
"P:\\tv\\Band of Brothers (2001)": 15129362120,
|
||||
"P:\\tv\\Quantum Leap (1989)": 39284023472,
|
||||
"P:\\tv\\Harley and the Davidsons": 76,
|
||||
"P:\\tv\\Rupaul's Drag Race All Stars": 60579323023,
|
||||
"P:\\tv\\Amazing Stories (2020)": 4281304451,
|
||||
"P:\\tv\\Murder She Wrote": 12095973826,
|
||||
"P:\\tv\\Kitchen Nightmares US": 56092851597,
|
||||
"P:\\tv\\Game Changer": 38317757866,
|
||||
"P:\\tv\\Taskmaster AU": 20527610746,
|
||||
"P:\\tv\\Fallout": 19686023936,
|
||||
"P:\\tv\\Young Sheldon": 21714069112,
|
||||
"P:\\tv\\Vice Principals (2016)": 18406955713,
|
||||
"P:\\tv\\Adventuring Academy": 62196997373,
|
||||
"P:\\tv\\Solar Opposites": 1138214210,
|
||||
"P:\\tv\\Pok\u00e9mon Concierge (2023)": 1134616527,
|
||||
"P:\\tv\\Better Call Saul": 31152560439,
|
||||
"P:\\tv\\Counterpart": 4875616955,
|
||||
"P:\\tv\\The Paper (2025)": 8102218176,
|
||||
"P:\\tv\\Chuck": 32193192829,
|
||||
"P:\\tv\\The Bachelorette": 9927266246,
|
||||
"P:\\tv\\Wandavision": 10099450034,
|
||||
"P:\\tv\\Pantheon": 13397374449,
|
||||
"P:\\tv\\The Gilded Age": 90505242840,
|
||||
"P:\\tv\\Gastronauts": 9365810750,
|
||||
"P:\\tv\\American Gods (2017)": 43921706762,
|
||||
"P:\\tv\\The IT Crowd (2006)": 9239572772,
|
||||
"P:\\tv\\Winning Time - The Rise of the Lakers Dynasty (2022)": 37911197652,
|
||||
"P:\\tv\\Monet's Slumber Party": 8253206091,
|
||||
"P:\\tv\\Walker": 5492500161,
|
||||
"P:\\tv\\Stargirl": 9507100884,
|
||||
"P:\\tv\\House of Guinness (2025)": 5444928896,
|
||||
"P:\\tv\\Father Brown": 18896564477,
|
||||
"P:\\tv\\Silo (2023)": 12897630564,
|
||||
"P:\\tv\\Your Honor (2020)": 25879839349,
|
||||
"P:\\tv\\Welcome to Wrexham": 66664948104,
|
||||
"P:\\tv\\Royal Pains (2009)": 1247586112,
|
||||
"P:\\tv\\The Continental (2023)": 1920206807,
|
||||
"P:\\tv\\Citadel": 2339699246,
|
||||
"P:\\tv\\The 10th Kingdom (2000)": 14174589505,
|
||||
"P:\\tv\\Parlor Room": 12022280605,
|
||||
"P:\\tv\\Its Always Sunny in Philadelphia": 84650830434,
|
||||
"P:\\tv\\Star Wars - Skeleton Crew (2024)": 2940779001,
|
||||
"P:\\tv\\Rupaul's Drag Race": 57149739065,
|
||||
"P:\\tv\\Only Murders in the Building (2021)": 2379838148,
|
||||
"P:\\tv\\Running Man": 10279755878,
|
||||
"P:\\tv\\Shetland": 18537045340,
|
||||
"P:\\tv\\Adults (2025)": 6845585714,
|
||||
"P:\\tv\\iCarly (2021)": 19966043984,
|
||||
"P:\\tv\\Villainous (2017)": 1961793524,
|
||||
"P:\\tv\\The Terminal List - Dark Wolf (2025)": 9939046560,
|
||||
"P:\\tv\\Ted Lasso (2020)": 52046307136,
|
||||
"P:\\tv\\Murderbot (2025)": 18338040970,
|
||||
"P:\\tv\\RuPaul's Drag Race Down Under": 27454793482,
|
||||
"P:\\tv\\Gravity Falls": 31900305156,
|
||||
"P:\\tv\\The Santa Clauses (2022)": 6400385164,
|
||||
"P:\\tv\\Marvel's The Punisher (2017)": 32242478897,
|
||||
"P:\\tv\\Dracula (2020)": 2147285239,
|
||||
"P:\\tv\\Extraordinary": 6934203888,
|
||||
"P:\\tv\\Cyberpunk - Edgerunners (2022)": 11313875182,
|
||||
"P:\\tv\\Rick and Morty": 31672318625,
|
||||
"P:\\tv\\Welcome to Chippendales (2022)": 10423545837,
|
||||
"P:\\tv\\Squid Game (2021)": 22082475135,
|
||||
"P:\\tv\\MobLand (2025)": 6622179548,
|
||||
"P:\\tv\\Taskmaster (NZ)": 71323320898,
|
||||
"P:\\tv\\The Newsroom": 27756667258,
|
||||
"P:\\tv\\The Pretender": 18425629462,
|
||||
"P:\\tv\\Hazbin Hotel (2024)": 10906489515,
|
||||
"P:\\tv\\Raised by wolves": 9720677524,
|
||||
"P:\\tv\\Tomb Raider - The Legend of Lara Croft": 9341088252,
|
||||
"P:\\tv\\Spartacus": 75639017886,
|
||||
"P:\\tv\\Worst Cooks in America (2010)": 22063867049,
|
||||
"P:\\tv\\Avenue 5": 12572813494,
|
||||
"P:\\tv\\Man Down (2013)": 5077144151,
|
||||
"P:\\tv\\Outlander": 27364180668,
|
||||
"P:\\tv\\The Eternaut": 17178505929,
|
||||
"P:\\tv\\Below Deck Down Under (2022)": 36006759742,
|
||||
"P:\\tv\\Dirty Laundry": 27626331672,
|
||||
"P:\\tv\\Chilling Adventures of Sabrina (2018)": 23147355371,
|
||||
"P:\\tv\\The Studio (2025)": 11530554023,
|
||||
"P:\\tv\\The Forsytes (2025)": 4034792830,
|
||||
"P:\\tv\\Platonic (2023)": 17488146510,
|
||||
"P:\\tv\\Love Island (US) (2019)": 20699120877,
|
||||
"P:\\tv\\Dark Side of the Ring": 11863132534,
|
||||
"P:\\tv\\The Day of the Jackal (2024)": 17787097381,
|
||||
"P:\\tv\\Utopia (AU)": 8691287022,
|
||||
"P:\\tv\\Sweetpea": 2706241673,
|
||||
"P:\\tv\\Dateline NBC (1992)": 19267231607,
|
||||
"P:\\tv\\Euphoria": 40925172559,
|
||||
"P:\\tv\\The Consultant (2023)": 74,
|
||||
"P:\\tv\\Titans (2018)": 31986198137,
|
||||
"P:\\tv\\Taskmaster": 142193364333,
|
||||
"P:\\tv\\Ink Master": 23329086486,
|
||||
"P:\\tv\\Dimension 20": 557729281243,
|
||||
"P:\\tv\\Continuum (2012)": 29352883496,
|
||||
"P:\\tv\\South Park": 70261225261,
|
||||
"P:\\tv\\Letterkenny": 63,
|
||||
"P:\\tv\\Ghosts (2019)": 40703143881,
|
||||
"P:\\tv\\Moon Knight": 10976093361,
|
||||
"P:\\tv\\Twisted Metal (2023)": 12547412897,
|
||||
"P:\\tv\\Extrapolations": 6690715385,
|
||||
"P:\\tv\\Quiet On Set - The Dark Side Of Kids TV": 12191520028,
|
||||
"P:\\tv\\Sh\u014dgun": 20899988683,
|
||||
"P:\\tv\\Taboo (2017)": 19309841226,
|
||||
"P:\\tv\\Ironheart (2025)": 3153557870,
|
||||
"P:\\tv\\DOTA - Dragon's Blood (2021)": 12538510766,
|
||||
"P:\\tv\\Knuckles": 2140786440,
|
||||
"P:\\tv\\Shoresy": 9900029120,
|
||||
"P:\\tv\\Impractical Jokers": 13357380400,
|
||||
"P:\\tv\\One More Time (2024)": 6434473461,
|
||||
"P:\\tv\\Crowd Control": 9644641207,
|
||||
"P:\\tv\\Dimension 20's Adventuring Party": 12002285238,
|
||||
"P:\\tv\\Special Ops Lioness": 9765393961,
|
||||
"P:\\tv\\Ted (2024)": 3024624414,
|
||||
"P:\\tv\\Mighty Nein (2025)": 6138965943,
|
||||
"P:\\tv\\Citadel - Diana": 13304679453,
|
||||
"P:\\tv\\Our Flag Means Death": 2107045664,
|
||||
"P:\\tv\\Make Some Noise": 25555591381,
|
||||
"P:\\tv\\Mayor of Kingstown (2021)": 65464041666,
|
||||
"P:\\tv\\The Take": 6020370013,
|
||||
"P:\\tv\\Agatha All Along": 3411637969,
|
||||
"P:\\tv\\The Amazing Digital Circus (2023)": 4739070191,
|
||||
"P:\\tv\\The Now": 836886747,
|
||||
"P:\\tv\\Poppa\u2019s House": 13794748297,
|
||||
"P:\\tv\\Married at First Sight (2014)": 30275711911,
|
||||
"P:\\tv\\The Closer": 47449608535,
|
||||
"P:\\tv\\Junior Taskmaster (2024)": 4133620030,
|
||||
"P:\\tv\\WondLa": 1399628000,
|
||||
"P:\\tv\\The Second Best Hospital in the Galaxy (2024)": 3636394169,
|
||||
"P:\\tv\\Being Human (2011)": 66311454464,
|
||||
"P:\\tv\\SCORPION": 54081802764,
|
||||
"P:\\tv\\The Goes Wrong Show (2019)": 3676343887,
|
||||
"P:\\tv\\See": 12316511887,
|
||||
"P:\\tv\\Dirk Gently's Holistic Detective Agency (2016)": 11935610182,
|
||||
"P:\\tv\\Tokyo Override (2024)": 3802255332,
|
||||
"P:\\tv\\Peacemaker (2022)": 13199970800,
|
||||
"P:\\tv\\The Falcon and The Winter Soldier (2021)": 11657055937,
|
||||
"P:\\tv\\Fargo (2014)": 93247402537,
|
||||
"P:\\tv\\Killer Cakes": 3673781461,
|
||||
"P:\\tv\\The Mandalorian": 36487773789,
|
||||
"P:\\tv\\Very Important People": 12237876110,
|
||||
"P:\\tv\\Smiling Friends": 5633340834,
|
||||
"P:\\tv\\Game Changers (2024)": 5880504271,
|
||||
"P:\\tv\\Star Strek Strange New Worlds": 13781151928,
|
||||
"P:\\tv\\Galavant": 12147863291,
|
||||
"P:\\tv\\She-Hulk Attorney at Law": 10233633417,
|
||||
"P:\\tv\\From Dusk Till Dawn - The Series (2014)": 5360771338,
|
||||
"P:\\tv\\The Journal of the Mysterious Creatures (2019)": 92,
|
||||
"P:\\tv\\Fallen (2024)": 4161867429,
|
||||
"P:\\tv\\Severance": 15044806873,
|
||||
"P:\\tv\\The Great (2020)": 22361386693,
|
||||
"P:\\tv\\What If": 21312022582,
|
||||
"P:\\tv\\Rupaul's Drag Race UK": 110914388896,
|
||||
"P:\\tv\\Game Of Thrones": 119681469870,
|
||||
"P:\\tv\\Belgravia - The Next Chapter": 8340040939,
|
||||
"P:\\tv\\Hitmen (2020)": 12274410846,
|
||||
"P:\\tv\\Haunted Hotel (2025)": 4735071992,
|
||||
"P:\\tv\\The Book of Boba Fett": 12039417291,
|
||||
"P:\\tv\\SAS Rogue Heroes (2022)": 10733559643,
|
||||
"P:\\tv\\Dwight in Shining Armor": 75,
|
||||
"P:\\tv\\Jury Duty": 8010062372,
|
||||
"P:\\tv\\Son of Zorn (2016)": 6780978712,
|
||||
"P:\\tv\\The Gentlemen (2024)": 5224500371,
|
||||
"P:\\tv\\Schmigadoon!": 6206632733,
|
||||
"P:\\tv\\The Drew Carey Show (1995)": 70,
|
||||
"P:\\tv\\Fired on Mars (2023)": 3590992124,
|
||||
"P:\\tv\\Black Bird (2022)": 5893929480,
|
||||
"P:\\tv\\Billions": 31141419259,
|
||||
"P:\\tv\\Reacher (2022)": 17521873037,
|
||||
"P:\\tv\\The Morning Show": 94311701751,
|
||||
"P:\\tv\\Secret Level": 2810124465,
|
||||
"P:\\tv\\The Boys": 68010010167,
|
||||
"P:\\tv\\Gordon Ramsay's Food Stars (2023)": 6344621632,
|
||||
"P:\\tv\\Death and Other Details": 17844763765,
|
||||
"P:\\tv\\Modern Family": 82788065200,
|
||||
"P:\\tv\\Married... with Children (1987)": 64228823786,
|
||||
"P:\\tv\\BattleBots": 61,
|
||||
"P:\\tv\\Silicon Valley (2014)": 63657428121,
|
||||
"P:\\tv\\Tires (2024)": 5375794389,
|
||||
"P:\\tv\\Creature Commandos (2024)": 2331424358,
|
||||
"P:\\tv\\Goosebumps (2023)": 8257419062,
|
||||
"P:\\tv\\The Fall of the House of Usher (2023)": 16454192941,
|
||||
"P:\\tv\\Passion for punchlines": 75514795,
|
||||
"P:\\tv\\The Queen's Gambit": 4100494817,
|
||||
"P:\\tv\\Suits LA (2025)": 22274831381,
|
||||
"P:\\tv\\Dune - Prophecy": 3330003290,
|
||||
"P:\\tv\\Unstable": 5444623642,
|
||||
"P:\\tv\\The Split": 7970767632,
|
||||
"P:\\tv\\Barry": 31934844666,
|
||||
"P:\\tv\\The Dragon Dentist": 11317084093,
|
||||
"P:\\tv\\Kevin Can F-k Himself": 11614889793
|
||||
}
|
||||
50
path_manager/config.xml
Normal file
50
path_manager/config.xml
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config>
|
||||
<general>
|
||||
<processing_folder>processing</processing_folder>
|
||||
<suffix> -EHX</suffix>
|
||||
<extensions>.mkv,.mp4</extensions>
|
||||
<ignore_tags>ehx,megusta</ignore_tags>
|
||||
<reduction_ratio_threshold>0.5</reduction_ratio_threshold>
|
||||
</general>
|
||||
<path_mappings>
|
||||
<map from="P:\tv" to="/mnt/plex/tv" />
|
||||
<map from="P:\anime" to="/mnt/plex/anime" />
|
||||
</path_mappings>
|
||||
<encode>
|
||||
<encoder default="nvenc">
|
||||
<av1_nvenc preset="p7" bit_depth="8" pix_fmt="yuv420p" />
|
||||
<hevc_nvenc preset="slow" bit_depth="10" pix_fmt="yuv420p10le" />
|
||||
</encoder>
|
||||
<cq>
|
||||
<tv_1080>28</tv_1080>
|
||||
<tv_720>32</tv_720>
|
||||
<movie_1080>32</movie_1080>
|
||||
<movie_720>34</movie_720>
|
||||
</cq>
|
||||
<fallback>
|
||||
<bitrate_1080>1500k</bitrate_1080>
|
||||
<maxrate_1080>1750k</maxrate_1080>
|
||||
<bufsize_1080>2750k</bufsize_1080>
|
||||
<bitrate_720>900k</bitrate_720>
|
||||
<maxrate_720>1250k</maxrate_720>
|
||||
<bufsize_720>1800k</bufsize_720>
|
||||
</fallback>
|
||||
<filters>
|
||||
<default>lanczos</default>
|
||||
<tv>bicubic</tv>
|
||||
</filters>
|
||||
</encode>
|
||||
<audio>
|
||||
<stereo>
|
||||
<low>64000</low>
|
||||
<medium>128000</medium>
|
||||
<high>160000</high>
|
||||
</stereo>
|
||||
<multi_channel>
|
||||
<low>384000</low>
|
||||
<medium>512000</medium>
|
||||
<high>640000</high>
|
||||
</multi_channel>
|
||||
</audio>
|
||||
</config>
|
||||
@ -5,9 +5,14 @@ GUI Path Manager for Batch Video Transcoder
|
||||
Allows easy browsing of folders and appending to paths.txt with encoding options.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add parent directory to path so we can import core modules
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import re
|
||||
@ -15,7 +20,7 @@ import json
|
||||
from core.config_helper import load_config_xml
|
||||
from core.logger_helper import setup_logger
|
||||
|
||||
logger = setup_logger(Path(__file__).parent / "logs")
|
||||
logger = setup_logger(Path(__file__).parent.parent / "logs")
|
||||
|
||||
class PathManagerGUI:
|
||||
def __init__(self, root):
|
||||
@ -23,14 +28,16 @@ class PathManagerGUI:
|
||||
self.root.title("Batch Transcoder - Path Manager")
|
||||
self.root.geometry("1100x700")
|
||||
|
||||
# Load config
|
||||
config_path = Path(__file__).parent / "config.xml"
|
||||
# Load config (from parent directory)
|
||||
config_path = Path(__file__).parent.parent / "config.xml"
|
||||
self.config = load_config_xml(config_path)
|
||||
self.path_mappings = self.config.get("path_mappings", {})
|
||||
# Convert path_mappings from list to dict for easier lookup
|
||||
path_mappings_list = self.config.get("path_mappings", [])
|
||||
self.path_mappings = {m["from"]: m["to"] for m in path_mappings_list} if isinstance(path_mappings_list, list) else path_mappings_list
|
||||
|
||||
# Paths file
|
||||
self.paths_file = Path(__file__).parent / "paths.txt"
|
||||
self.transcode_bat = Path(__file__).parent / "transcode.bat"
|
||||
# Paths file (in root directory)
|
||||
self.paths_file = Path(__file__).parent.parent / "paths.txt"
|
||||
self.transcode_bat = Path(__file__).parent.parent / "transcode.bat"
|
||||
|
||||
# Current selected folder
|
||||
self.selected_folder = None
|
||||
@ -40,7 +47,7 @@ class PathManagerGUI:
|
||||
self.added_folders = set() # Folders that are in paths.txt
|
||||
|
||||
# Cache for folder data - split per category
|
||||
self.cache_dir = Path(__file__).parent / ".cache"
|
||||
self.cache_dir = Path(__file__).parent.parent / ".cache"
|
||||
self.cache_dir.mkdir(exist_ok=True)
|
||||
self.folder_cache = {} # Only current category in memory: {folder_path: size}
|
||||
self.scan_in_progress = False
|
||||
|
||||
3
path_manager/logs/conversion.log
Normal file
3
path_manager/logs/conversion.log
Normal file
@ -0,0 +1,3 @@
|
||||
{"timestamp": "2026-01-02T03:44:59Z", "level": "INFO", "message": "No cache file for tv", "module": "gui_path_manager", "funcName": "_load_cache", "line": 213}
|
||||
{"timestamp": "2026-01-02T03:45:35Z", "level": "INFO", "message": "No cache file for tv", "module": "gui_path_manager", "funcName": "_load_cache", "line": 215}
|
||||
{"timestamp": "2026-01-02T03:45:43Z", "level": "INFO", "message": "No cache file for movies", "module": "gui_path_manager", "funcName": "_load_cache", "line": 215}
|
||||
@ -1,11 +1,4 @@
|
||||
--m cq "P:\movies\Starship Troopers (1997)"
|
||||
--m cq "P:\movies\John Wick - Chapter 3 - Parabellum (2019)"
|
||||
--m cq "P:\movies\John Wick - Chapter 2 (2017)"
|
||||
--m cq "P:\movies\Belle (2021)"
|
||||
--m cq "P:\movies\TAYLOR SWIFT THE ERAS TOUR (2023)"
|
||||
--m cq "P:\movies\Ferris Bueller's Day Off (1986)"
|
||||
--m cq --r 720 "P:\movies\The Baker (2023)"
|
||||
--m cq "P:\movies\The Losers (2010)"
|
||||
--m cq "P:\movies\Violent Night (2022)"
|
||||
--m cq "P:\movies\Scott Pilgrim vs. the World (2010)"
|
||||
--m cq "P:\movies\Small Soldiers (1998)"
|
||||
--m cq --cq 28 "P:\movies\Pirates of the Caribbean - At World's End (2007)"
|
||||
--m cq --cq 28 "P:\movies\Pirates of the Caribbean - The Curse of the Black Pearl (2003)"
|
||||
--m cq --cq 28 "P:\movies\Pirates of the Caribbean - Dead Men Tell No Tales (2017)"
|
||||
--m cq --cq 28 "P:\movies\Pirates of the Caribbean - On Stranger Tides (2011)"
|
||||
|
||||
9
paths.txt
Normal file
9
paths.txt
Normal file
@ -0,0 +1,9 @@
|
||||
"P:\movies\Wolf Children (2012)"
|
||||
"P:\movies\The Intern (2015)"
|
||||
"P:\movies\The Suicide Squad (2021)"
|
||||
"P:\movies\Venom - The Last Dance (2024)"
|
||||
--r 720 "P:\movies\Meet the Fockers (2004)"
|
||||
--r 720 "P:\movies\Meet the Parents (2000)"
|
||||
--r 720 "P:\movies\Little Fockers (2010)"
|
||||
--r 1080 "P:\movies\Premium Rush (2012)"
|
||||
"P:\movies\Captain America - Brave New World (2025)"
|
||||
Loading…
x
Reference in New Issue
Block a user