a lot of changes
This commit is contained in:
parent
0d415c7e49
commit
a2e2ee45f5
406
.cache/.cache_anime.json
Normal file
406
.cache/.cache_anime.json
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
{
|
||||||
|
"P:\\anime\\The Greatest Demon Lord Is Reborn as a Typical Nobody": 5968497118,
|
||||||
|
"P:\\anime\\So, I Can't Play H!": 2528562389,
|
||||||
|
"P:\\anime\\The Ossan Newbie Adventurer, Trained to Death by the Most Powerful Party, Became Invincible": 4634410199,
|
||||||
|
"P:\\anime\\Kaifuku Jutsushi no Yarinaoshi": 3384572882,
|
||||||
|
"P:\\anime\\Gleipnir": 6112213174,
|
||||||
|
"P:\\anime\\Banished from the Hero's Party, I Decided to Live a Quiet Life in the Countryside (2021)": 10648931598,
|
||||||
|
"P:\\anime\\I'm Standing on a Million Lives": 8789518857,
|
||||||
|
"P:\\anime\\Lv1 Maou to One Room Yuusha": 2352794776,
|
||||||
|
"P:\\anime\\The 8th son! Are you kidding me! (2020)": 2984369205,
|
||||||
|
"P:\\anime\\Good Bye, Dragon Life (2024)": 3039400658,
|
||||||
|
"P:\\anime\\Shinchou Yuusha": 7936747223,
|
||||||
|
"P:\\anime\\Battle Game in 5 Seconds": 5594736882,
|
||||||
|
"P:\\anime\\Infinite Dendrogram": 13754309450,
|
||||||
|
"P:\\anime\\Magical Senpai": 3401214483,
|
||||||
|
"P:\\anime\\Noumin Kanren no Skill bakka Agetetara Nazeka Tsuyoku Natta": 3858206539,
|
||||||
|
"P:\\anime\\No Game No Life": 2517488069,
|
||||||
|
"P:\\anime\\Keikenzumi na Kimi to": 8117430342,
|
||||||
|
"P:\\anime\\Broken Blade": 3859920179,
|
||||||
|
"P:\\anime\\Gachiakuta (2025)": 10697119170,
|
||||||
|
"P:\\anime\\Princess Connect": 12937047999,
|
||||||
|
"P:\\anime\\ReLife": 1565479993,
|
||||||
|
"P:\\anime\\Shinobi no Ittoki (2022)": 3896251319,
|
||||||
|
"P:\\anime\\World Break - Aria of Curse for a Holy Swordsman (2015)": 5244615290,
|
||||||
|
"P:\\anime\\Chillin' in Another World with Level 2 Super Cheat Powers": 4646756870,
|
||||||
|
"P:\\anime\\Vinland Saga (2019)": 4723680531,
|
||||||
|
"P:\\anime\\Bucchigiri!!": 3895924967,
|
||||||
|
"P:\\anime\\Kyoukai Senki": 6697938339,
|
||||||
|
"P:\\anime\\Shachou Battle No Jikan Desu": 7376322052,
|
||||||
|
"P:\\anime\\The Foolish Angel Dances With the Devil": 4048585416,
|
||||||
|
"P:\\anime\\Bokutachi no Remake": 1499113600,
|
||||||
|
"P:\\anime\\Handyman Saitou in Another World": 3817097529,
|
||||||
|
"P:\\anime\\Welcome to Demon School! Iruma-kun (2019)": 26889513042,
|
||||||
|
"P:\\anime\\Alya Sometimes Hides Her Feelings in Russian": 4232637768,
|
||||||
|
"P:\\anime\\Dead Mount Death Play (2023)": 9605188624,
|
||||||
|
"P:\\anime\\Demon Slayer - Kimetsu no Yaiba (2019)": 21376385353,
|
||||||
|
"P:\\anime\\Kaiju No. 8": 8770165001,
|
||||||
|
"P:\\anime\\DNAngel": 6235958953,
|
||||||
|
"P:\\anime\\Adam's Sweet Agony": 71,
|
||||||
|
"P:\\anime\\Leadale no Daichi nite": 1473622878,
|
||||||
|
"P:\\anime\\Isekai de Cheat Skill": 4311581943,
|
||||||
|
"P:\\anime\\Val x Love (2019)": 634008306,
|
||||||
|
"P:\\anime\\SHOSHIMIN - How to Become Ordinary": 21152251460,
|
||||||
|
"P:\\anime\\UchiMusume": 6085582077,
|
||||||
|
"P:\\anime\\Kino no Tabi": 3227343894,
|
||||||
|
"P:\\anime\\Shokei Shoujo no Virgin Road": 2660059361,
|
||||||
|
"P:\\anime\\Arknights": 5905302117,
|
||||||
|
"P:\\anime\\Don't Toy With Me Miss Nagatoro": 4485844086,
|
||||||
|
"P:\\anime\\Death March": 2905475121,
|
||||||
|
"P:\\anime\\Ascendance of a Bookworm": 5407349860,
|
||||||
|
"P:\\anime\\Loner Life in Another World": 25508136936,
|
||||||
|
"P:\\anime\\Blast of Tempest": 6315813655,
|
||||||
|
"P:\\anime\\The Eminence in Shadow": 12848813083,
|
||||||
|
"P:\\anime\\I'm Living with an Otaku NEET Kunoichi!! (2025)": 6529531138,
|
||||||
|
"P:\\anime\\Rent a girlfriend": 5319970861,
|
||||||
|
"P:\\anime\\B The Beginning": 9274520504,
|
||||||
|
"P:\\anime\\FLCL": 3233214173,
|
||||||
|
"P:\\anime\\Demon Lord 2099 (2024)": 4298604298,
|
||||||
|
"P:\\anime\\Fairy Gone": 5688255513,
|
||||||
|
"P:\\anime\\Fantasy \u00d7 Hunter (2021)": 3990876164,
|
||||||
|
"P:\\anime\\Outbreak Company": 6048060793,
|
||||||
|
"P:\\anime\\4 Cut Hero": 4259631497,
|
||||||
|
"P:\\anime\\Peach Boy Riverside": 1507820436,
|
||||||
|
"P:\\anime\\Lazarus (2025)": 5916959141,
|
||||||
|
"P:\\anime\\Ragna Crimson": 9249575950,
|
||||||
|
"P:\\anime\\Jujutsu Kaisen": 20957528147,
|
||||||
|
"P:\\anime\\Harem in the Labyrinth of Another World": 2871442057,
|
||||||
|
"P:\\anime\\Sekirei (2008)": 9623765425,
|
||||||
|
"P:\\anime\\Reign of the Seven Spellblades": 4104496247,
|
||||||
|
"P:\\anime\\By the Grace of the Gods": 5345602025,
|
||||||
|
"P:\\anime\\Captain Earth": 5810590243,
|
||||||
|
"P:\\anime\\Air Gear (2006)": 8918228002,
|
||||||
|
"P:\\anime\\My Instant Death Ability Is So Overpowered, No One in This Other World Stands a Chance Against Me!": 4454494342,
|
||||||
|
"P:\\anime\\Maburaho": 9553356823,
|
||||||
|
"P:\\anime\\One Pace": 5154953305,
|
||||||
|
"P:\\anime\\Love Tyrant": 2852328170,
|
||||||
|
"P:\\anime\\Sword Art Online (2012)": 42953654496,
|
||||||
|
"P:\\anime\\My Hero Academia": 91570180658,
|
||||||
|
"P:\\anime\\SAKAMOTO DAYS (2025)": 12135764735,
|
||||||
|
"P:\\anime\\No Guns Life (2019)": 12216429324,
|
||||||
|
"P:\\anime\\Undead Unluck": 8811674477,
|
||||||
|
"P:\\anime\\Guilty Crown": 6872382355,
|
||||||
|
"P:\\anime\\Radiant (2018)": 9278741705,
|
||||||
|
"P:\\anime\\Tokyo Revengers": 9193065093,
|
||||||
|
"P:\\anime\\How Not to Summon a Demon Lord": 6700089914,
|
||||||
|
"P:\\anime\\The Demon Sword Master of Excalibur Academy": 3453095058,
|
||||||
|
"P:\\anime\\Meikyuu Black Company": 2658074731,
|
||||||
|
"P:\\anime\\Toradora!": 6558115868,
|
||||||
|
"P:\\anime\\Maken-Ki! Battling Venus (2011)": 3500680574,
|
||||||
|
"P:\\anime\\Why Does Nobody Remember Me in This World! (2024)": 4152176021,
|
||||||
|
"P:\\anime\\YU-NO - A Girl Who Chants Love at the Bound of This World (2019)": 6254598586,
|
||||||
|
"P:\\anime\\Heroic Age": 7205431055,
|
||||||
|
"P:\\anime\\Solo Leveling": 16655700087,
|
||||||
|
"P:\\anime\\Cop Craft": 1913887249,
|
||||||
|
"P:\\anime\\Urusei Yatsura (2022)": 7969472192,
|
||||||
|
"P:\\anime\\Deepy Insanity The Lost Child": 8187291543,
|
||||||
|
"P:\\anime\\I'm the Evil Lord of an Intergalactic Empire! (2025)": 4135726123,
|
||||||
|
"P:\\anime\\King's Raid": 9278771931,
|
||||||
|
"P:\\anime\\Robotics;Notes": 5662173084,
|
||||||
|
"P:\\anime\\Kemono Jihen": 7461581399,
|
||||||
|
"P:\\anime\\Noragami": 7371770717,
|
||||||
|
"P:\\anime\\Food Wars": 39136542103,
|
||||||
|
"P:\\anime\\Kemono Michi Rise Up (2019)": 7529875102,
|
||||||
|
"P:\\anime\\Akudama Drive": 7276183519,
|
||||||
|
"P:\\anime\\Heavenly Delusion": 4930379667,
|
||||||
|
"P:\\anime\\High School of the Dead": 6051229299,
|
||||||
|
"P:\\anime\\Hero Without a Class - Who Even Needs Skills!! (2025)": 11550176573,
|
||||||
|
"P:\\anime\\Tears to Tiara": 6916843507,
|
||||||
|
"P:\\anime\\Mobile Suit Gundam - Iron-Blooded Orphans": 22688263314,
|
||||||
|
"P:\\anime\\Murenase! Seton Gakuen": 8774144557,
|
||||||
|
"P:\\anime\\The Brilliant Healer's New Life in the Shadows": 4196047668,
|
||||||
|
"P:\\anime\\The Kingdoms of Ruin": 4953422715,
|
||||||
|
"P:\\anime\\The Great Jahy Will Not Be Defeated! (2021)": 7300409701,
|
||||||
|
"P:\\anime\\D.Gray-man": 4272945740,
|
||||||
|
"P:\\anime\\Ya Boy Kongming! (2022)": 2371722402,
|
||||||
|
"P:\\anime\\Grendizer U": 8793468670,
|
||||||
|
"P:\\anime\\ReZERO -Starting Life in Another World": 34808814228,
|
||||||
|
"P:\\anime\\Taboo Tattoo": 3570322719,
|
||||||
|
"P:\\anime\\KamiKatsu - Working for God in a Godless World (2023)": 4033372211,
|
||||||
|
"P:\\anime\\Boogiepop Phantom": 3775812052,
|
||||||
|
"P:\\anime\\Hamatora": 6142541816,
|
||||||
|
"P:\\anime\\A Boring World Where the Concept of Dirty Jokes Doesn't Exist": 4384721870,
|
||||||
|
"P:\\anime\\Tribe Nine": 7128583972,
|
||||||
|
"P:\\anime\\Build Divide Code Black": 7356540221,
|
||||||
|
"P:\\anime\\I'm Quitting Heroing (2022)": 2641278879,
|
||||||
|
"P:\\anime\\THE NEW GATE (2024)": 2272505431,
|
||||||
|
"P:\\anime\\I Was Reincarnated as the 7th Prince so I Can Take My Time Perfecting My Magical Ability (2024)": 13202089851,
|
||||||
|
"P:\\anime\\Sword Art Online Alternative - Gun Gale Online": 3803411762,
|
||||||
|
"P:\\anime\\SANDA (2025)": 6601681293,
|
||||||
|
"P:\\anime\\Dolls' Frontline": 7376834741,
|
||||||
|
"P:\\anime\\Casshern Sins (2008)": 9460351881,
|
||||||
|
"P:\\anime\\Revenger": 2130039433,
|
||||||
|
"P:\\anime\\The Great Cleric": 3320612535,
|
||||||
|
"P:\\anime\\Spice and Wolf - MERCHANT MEETS THE WISE WOLF": 8173191986,
|
||||||
|
"P:\\anime\\Villainess Level 99 - I May Be the Hidden Boss but I'm Not the Demon Lord (2024)": 3561222609,
|
||||||
|
"P:\\anime\\Muv-Luv Alternative": 13357929759,
|
||||||
|
"P:\\anime\\And You Thought There Is Never a Girl Online": 4452676547,
|
||||||
|
"P:\\anime\\Grisaia no Kajitsu": 5151626219,
|
||||||
|
"P:\\anime\\Yandere Dark Elf - She Chased Me All the Way From Another World (2025)": 6378005867,
|
||||||
|
"P:\\anime\\Tatoeba Last Dungeon": 4836583236,
|
||||||
|
"P:\\anime\\ZENSHU (2025)": 15428885323,
|
||||||
|
"P:\\anime\\Shikizakura": 2018217197,
|
||||||
|
"P:\\anime\\Magical Warfare": 3590141409,
|
||||||
|
"P:\\anime\\The Most Notorious Talker Runs the World's Greatest Clan": 15191321279,
|
||||||
|
"P:\\anime\\Tales of Wedding Rings": 20310093214,
|
||||||
|
"P:\\anime\\One-Punch Man (2015)": 21480447907,
|
||||||
|
"P:\\anime\\Let This Grieving Soul Retire!": 24270650972,
|
||||||
|
"P:\\anime\\DEAD DEAD DEMONS DEDEDEDE DESTRUCTION": 6024955773,
|
||||||
|
"P:\\anime\\Brave Bang Bravern!": 5660773541,
|
||||||
|
"P:\\anime\\No Longer Allowed in Another World": 3507605900,
|
||||||
|
"P:\\anime\\Black Lagoon": 3530474230,
|
||||||
|
"P:\\anime\\Lapis ReLights": 4027545928,
|
||||||
|
"P:\\anime\\Rokka Braves of the Six Flowers": 5477020823,
|
||||||
|
"P:\\anime\\That Time I Got Reincarnated as a Slime (2018)": 21976147633,
|
||||||
|
"P:\\anime\\Your Lie in April (2014)": 3698563140,
|
||||||
|
"P:\\anime\\Amagi Brilliant Park": 2905611585,
|
||||||
|
"P:\\anime\\Ore dake Haireru Kakushi Dungeon": 4974018022,
|
||||||
|
"P:\\anime\\Overlord": 19235478562,
|
||||||
|
"P:\\anime\\Nozo X Kimi": 63,
|
||||||
|
"P:\\anime\\Life With an Ordinary Guy Who Reincarnated Into a Total Fantasy Knockout (2022)": 7944272179,
|
||||||
|
"P:\\anime\\Am I Actually the Strongest": 4272833723,
|
||||||
|
"P:\\anime\\Btooom!": 3101208509,
|
||||||
|
"P:\\anime\\Trigun Stampede": 13265836559,
|
||||||
|
"P:\\anime\\Overflow": 1602757167,
|
||||||
|
"P:\\anime\\Zom 100 - Bucket List of the Dead (2023)": 4941498609,
|
||||||
|
"P:\\anime\\Dusk Beyond the End of the World (2025)": 7418826849,
|
||||||
|
"P:\\anime\\Somali to Mori no Kamisama": 1461553114,
|
||||||
|
"P:\\anime\\Armed Girl's Machiavellism": 5134830058,
|
||||||
|
"P:\\anime\\Kaijin Kaihatsu-bu no Kuroitsu-san": 8782789658,
|
||||||
|
"P:\\anime\\Beheneko - The Elf-Girl's Cat Is Secretly an S-Ranked Monster! (2025)": 16419141963,
|
||||||
|
"P:\\anime\\Tatsuki Fujimoto 17-26 (2025)": 2165081620,
|
||||||
|
"P:\\anime\\I Couldn't Become a Hero, so I Reluctantly Decided To Get a Job (2013)": 3099289383,
|
||||||
|
"P:\\anime\\Etotama": 3578916471,
|
||||||
|
"P:\\anime\\Ranking of Kings": 14621466110,
|
||||||
|
"P:\\anime\\Divine Gate (2016)": 5316940542,
|
||||||
|
"P:\\anime\\Chained Soldier": 5342727117,
|
||||||
|
"P:\\anime\\Mashle": 17394820927,
|
||||||
|
"P:\\anime\\Frieren - Beyond Journey's End": 11535858054,
|
||||||
|
"P:\\anime\\Sakugan": 7367853913,
|
||||||
|
"P:\\anime\\Summer Time Render (2022)": 12201595066,
|
||||||
|
"P:\\anime\\Is It Wrong to Try to Pick Up Girls in a Dungeon": 14990233709,
|
||||||
|
"P:\\anime\\The Strongest Magician in the Demon Lord's Army was a Human": 2371668942,
|
||||||
|
"P:\\anime\\Hokkaido Gals Are Super Adorable!": 3798126702,
|
||||||
|
"P:\\anime\\Tsugumomo": 3588842344,
|
||||||
|
"P:\\anime\\Koi wa Sekai Seifuku no Ato de": 8842484921,
|
||||||
|
"P:\\anime\\Pseudo Harem": 11144999080,
|
||||||
|
"P:\\anime\\Rascal Does Not Dream of Bunny Girl Senpai (2018)": 22453345353,
|
||||||
|
"P:\\anime\\Dr.Stone": 8061461697,
|
||||||
|
"P:\\anime\\Kyuukyoku Shinka Shita Full Dive RPG ga Genjitsu yori mo Kusogee Dattara": 2946703318,
|
||||||
|
"P:\\anime\\The Daily Life of the Immortal King": 16185152763,
|
||||||
|
"P:\\anime\\Medaka Box": 7415352854,
|
||||||
|
"P:\\anime\\ID Invaded": 3399670994,
|
||||||
|
"P:\\anime\\Arifureta - From Commonplace to World's Strongest (2019)": 25705179606,
|
||||||
|
"P:\\anime\\So I'm a Spider, So What\uf025": 8812944012,
|
||||||
|
"P:\\anime\\Mobile Suit Gundam GQuuuuuuX (2025)": 6651663995,
|
||||||
|
"P:\\anime\\Kyokou Suiri": 8904306316,
|
||||||
|
"P:\\anime\\Punch Line": 3182400207,
|
||||||
|
"P:\\anime\\Spice and Wolf (2008)": 19280134842,
|
||||||
|
"P:\\anime\\Giant Beasts of Ars (2023)": 5083927761,
|
||||||
|
"P:\\anime\\Xam'd - Lost Memories (2008)": 5865586067,
|
||||||
|
"P:\\anime\\Fantasia Sango - Realm of Legends": 4577794096,
|
||||||
|
"P:\\anime\\The Devil Is a Part-Timer! (2013)": 12904787079,
|
||||||
|
"P:\\anime\\I'm the Villainess, So I'm Taming the Final Boss": 8846571865,
|
||||||
|
"P:\\anime\\The Faraway Paladin": 8109669375,
|
||||||
|
"P:\\anime\\2.5 Dimensional Seduction (2024)": 25882183452,
|
||||||
|
"P:\\anime\\Hortensia Saga": 7471222614,
|
||||||
|
"P:\\anime\\Yoasobi Gurashi! (2024)": 59,
|
||||||
|
"P:\\anime\\PLUTO (2023)": 10080474308,
|
||||||
|
"P:\\anime\\Code Geass - Roz\u00e9 of the Recapture": 12458298795,
|
||||||
|
"P:\\anime\\Sabikui Bisco": 8845023203,
|
||||||
|
"P:\\anime\\Dimension W": 6030623909,
|
||||||
|
"P:\\anime\\My One-Hit Kill Sister": 3952345545,
|
||||||
|
"P:\\anime\\Nura Rise of the Yokai Clan (2010)": 6880946329,
|
||||||
|
"P:\\anime\\Wandering Witch - The Journey of Elaina (2020)": 4009196941,
|
||||||
|
"P:\\anime\\Our Last Crusade or the Rise of a New World (2020)": 22376200276,
|
||||||
|
"P:\\anime\\Chillin' in My 30s after Getting Fired from the Demon King's Army (2023)": 8923284328,
|
||||||
|
"P:\\anime\\The Legend of the Legendary Heroes": 3321747819,
|
||||||
|
"P:\\anime\\Viral Hit (2024)": 4222663705,
|
||||||
|
"P:\\anime\\My Isekai Life (2023)": 5128126734,
|
||||||
|
"P:\\anime\\Danganronpa": 20640550319,
|
||||||
|
"P:\\anime\\The Apothecary Diaries (2023)": 13984546814,
|
||||||
|
"P:\\anime\\Do You Love Your Mom and Her Two-Hit Multi-Target Attacks! (2019)": 7458697426,
|
||||||
|
"P:\\anime\\Tondemo Skill de Isekai Hourou Meshi": 3743859331,
|
||||||
|
"P:\\anime\\The Unwanted Undead Adventurer": 3551145665,
|
||||||
|
"P:\\anime\\Horimiya": 4883874524,
|
||||||
|
"P:\\anime\\Majutsushi Orphen Hagure Tabi": 8043422997,
|
||||||
|
"P:\\anime\\Mahouka Koukou no Rettousei": 12595036509,
|
||||||
|
"P:\\anime\\Classroom of the elite": 4317927091,
|
||||||
|
"P:\\anime\\'Tis Time for Torture, Princess": 3456434199,
|
||||||
|
"P:\\anime\\To be Hero X (2025)": 9231210440,
|
||||||
|
"P:\\anime\\Enen no Shouboutai": 17624413412,
|
||||||
|
"P:\\anime\\New Saga (2025)": 3640546484,
|
||||||
|
"P:\\anime\\Triage X": 2888953825,
|
||||||
|
"P:\\anime\\The Fable": 6468034435,
|
||||||
|
"P:\\anime\\Good Night World": 6328301164,
|
||||||
|
"P:\\anime\\As a Reincarnated Aristocrat, I'll Use My Appraisal Skill To Rise in the World": 7971179978,
|
||||||
|
"P:\\anime\\Strike the Blood": 16209826871,
|
||||||
|
"P:\\anime\\Bakemonogatari": 3058292879,
|
||||||
|
"P:\\anime\\The Quintessential Quintuplets": 9307789725,
|
||||||
|
"P:\\anime\\Record of Ragnarok": 4724846924,
|
||||||
|
"P:\\anime\\The Case Study of Vanitas (2021)": 23448375312,
|
||||||
|
"P:\\anime\\Seven Mortal Sins": 6941414132,
|
||||||
|
"P:\\anime\\SPY x FAMILY (2022)": 32564008225,
|
||||||
|
"P:\\anime\\Fumetsu no Anata e": 30402249614,
|
||||||
|
"P:\\anime\\Edens Zero (2021)": 24008630035,
|
||||||
|
"P:\\anime\\Tokyo Majin": 8240109758,
|
||||||
|
"P:\\anime\\Saga of Tanya the Evil (2017)": 5356263633,
|
||||||
|
"P:\\anime\\Isekai Cheat Magician": 6956708991,
|
||||||
|
"P:\\anime\\Devils and Realist": 9782071975,
|
||||||
|
"P:\\anime\\Hero Return": 3795654831,
|
||||||
|
"P:\\anime\\Log Horizon": 30428201748,
|
||||||
|
"P:\\anime\\Akame ga Kill!": 9281445041,
|
||||||
|
"P:\\anime\\Dog & Scissors": 4070030221,
|
||||||
|
"P:\\anime\\Chainsaw Man (2022)": 3962550947,
|
||||||
|
"P:\\anime\\Toaru Kagaku no Accelerator": 4470774794,
|
||||||
|
"P:\\anime\\The Aristocrat\u2019s Otherworldly Adventure - Serving Gods Who Go Too Far (2023)": 14415952754,
|
||||||
|
"P:\\anime\\The Misfit of Demon King Academy": 11880070022,
|
||||||
|
"P:\\anime\\Chobits": 3750727774,
|
||||||
|
"P:\\anime\\Devil May Cry (2025)": 2598556744,
|
||||||
|
"P:\\anime\\Bloodivores": 1944037383,
|
||||||
|
"P:\\anime\\Plunderer": 11316610908,
|
||||||
|
"P:\\anime\\Steins;Gate": 4725618067,
|
||||||
|
"P:\\anime\\Shomin Sample": 8260184285,
|
||||||
|
"P:\\anime\\Oshi No Ko": 3823754198,
|
||||||
|
"P:\\anime\\Summoned to Another World for a Second Time (2023)": 8794846955,
|
||||||
|
"P:\\anime\\KonoSuba": 24362870774,
|
||||||
|
"P:\\anime\\Deadman Wonderland": 2631932266,
|
||||||
|
"P:\\anime\\Sword of the Demon Hunter - Kijin Gentosho (2025)": 25576883003,
|
||||||
|
"P:\\anime\\Call of the Night (2022)": 2104316829,
|
||||||
|
"P:\\anime\\Chaos Dragon (2015)": 8975083681,
|
||||||
|
"P:\\anime\\Sono Bisque Doll wa Koi o Suru": 4778669831,
|
||||||
|
"P:\\anime\\My Life as Inukai-san's Dog (2023)": 3968549174,
|
||||||
|
"P:\\anime\\Tensai Ouji no Akaji Kokka Saisei Jutsu": 7768516948,
|
||||||
|
"P:\\anime\\Mr. Villain's Day Off": 5828680585,
|
||||||
|
"P:\\anime\\My Girlfriend is Shobitch": 4575335545,
|
||||||
|
"P:\\anime\\Junketsu no Maria (Maria the Virgin Witch)": 3102318111,
|
||||||
|
"P:\\anime\\I Left my A-Rank Party to Help My Former Students Reach the Dungeon Depths!": 8709504239,
|
||||||
|
"P:\\anime\\Sunday Without God": 4038886818,
|
||||||
|
"P:\\anime\\Tate no Yuusha no Nariagari": 24178141736,
|
||||||
|
"P:\\anime\\You are Ms. Servant (2024)": 4971082404,
|
||||||
|
"P:\\anime\\Orient": 7905080784,
|
||||||
|
"P:\\anime\\Joran - The Princess of Snow and Blood (2021)": 4285203115,
|
||||||
|
"P:\\anime\\BOFURI I Don't Want to Get Hurt, so I'll Max Out My Defense. (2020)": 3933320248,
|
||||||
|
"P:\\anime\\Girls Bravo": 5883170984,
|
||||||
|
"P:\\anime\\Tantei wa Mou, Shindeiru": 1474340872,
|
||||||
|
"P:\\anime\\Black Summoner": 4567460661,
|
||||||
|
"P:\\anime\\Wistoria - Wand and Sword (2024)": 6857088069,
|
||||||
|
"P:\\anime\\Assassins Pride": 4073372515,
|
||||||
|
"P:\\anime\\GATE": 6973714236,
|
||||||
|
"P:\\anime\\Hunter x Hunter (2011)": 28776368971,
|
||||||
|
"P:\\anime\\WITCH WATCH (2025)": 36023680385,
|
||||||
|
"P:\\anime\\VanDread (2000)": 7637961912,
|
||||||
|
"P:\\anime\\Gibiate": 8871671340,
|
||||||
|
"P:\\anime\\Tojima Tanzaburo Wants to Be a Kamen Rider": 17458489042,
|
||||||
|
"P:\\anime\\Chiikawa": 201629499,
|
||||||
|
"P:\\anime\\Skeleton Knight in Another World": 4720673665,
|
||||||
|
"P:\\anime\\Welcome to the N.H.K. (2006)": 2905064932,
|
||||||
|
"P:\\anime\\Seirei Gensouki": 12072776481,
|
||||||
|
"P:\\anime\\Even Given the Worthless Appraiser Class, I'm Actually the Strongest (2025)": 3877147709,
|
||||||
|
"P:\\anime\\Seraph of the End": 8836558304,
|
||||||
|
"P:\\anime\\86 - Eighty Six (2021)": 11346777283,
|
||||||
|
"P:\\anime\\I May Be a Guild Receptionist, But I'll Solo Any Boss to Clock Out on Time (2025)": 3401364461,
|
||||||
|
"P:\\anime\\That Time I Got Reincarnated as a Slime (2018": 39885493376,
|
||||||
|
"P:\\anime\\Magi Adventure of Sinbad": 4379068780,
|
||||||
|
"P:\\anime\\Gosick": 7565536865,
|
||||||
|
"P:\\anime\\Lord Marksman and Vanadis": 3650176287,
|
||||||
|
"P:\\anime\\Aesthetica of a Rogue Hero": 3926285815,
|
||||||
|
"P:\\anime\\The Wrong Way To Use Healing Magic (2024)": 4887942602,
|
||||||
|
"P:\\anime\\Undead Girl Murder Farce": 1425415656,
|
||||||
|
"P:\\anime\\World's End Harem (2021)": 3758753966,
|
||||||
|
"P:\\anime\\The Healer who Was Banished From His Party, Is, In Fact, The Strongest": 3180656302,
|
||||||
|
"P:\\anime\\Great Pretender": 6791244533,
|
||||||
|
"P:\\anime\\Bye Bye, Earth (2024)": 6901980665,
|
||||||
|
"P:\\anime\\To LOVE-Ru (2008)": 24039134215,
|
||||||
|
"P:\\anime\\My Senpai is Annoying": 2636570562,
|
||||||
|
"P:\\anime\\Machikado Mazoku": 2372896297,
|
||||||
|
"P:\\anime\\Tsukimichi - Moonlit Fantasy (2021)": 6355047942,
|
||||||
|
"P:\\anime\\Beyond the Boundary (2013)": 10548084129,
|
||||||
|
"P:\\anime\\The God of High School": 5246905321,
|
||||||
|
"P:\\anime\\Suicide Squad Isekai (2024)": 9987227506,
|
||||||
|
"P:\\anime\\\u00dcbel Blatt (2025)": 4818553182,
|
||||||
|
"P:\\anime\\Fractale": 2349352593,
|
||||||
|
"P:\\anime\\Rosario + Vampire (2008)": 10733216754,
|
||||||
|
"P:\\anime\\THE RED RANGER Becomes an Adventurer in Another World (2025)": 4815193468,
|
||||||
|
"P:\\anime\\Wise Man's Grandchild (2019)": 5251808321,
|
||||||
|
"P:\\anime\\Liar Liar (2023)": 3742043301,
|
||||||
|
"P:\\anime\\Listeners": 7623419250,
|
||||||
|
"P:\\anime\\Tokyo Ghoul": 3007724887,
|
||||||
|
"P:\\anime\\Ninja Kamui": 5301196868,
|
||||||
|
"P:\\anime\\Apocalypse Hotel (2025)": 7672503907,
|
||||||
|
"P:\\anime\\Megami-ryou no Ryoubo-kun": 1113458183,
|
||||||
|
"P:\\anime\\Demon King Daimao": 3779405140,
|
||||||
|
"P:\\anime\\Shikkakumon no Saikyou Kenja": 12316965814,
|
||||||
|
"P:\\anime\\The Dawn of the Witch (2022)": 5090411078,
|
||||||
|
"P:\\anime\\An Archdemon's Dilemma - How To Love Your Elf Bride": 4148806570,
|
||||||
|
"P:\\anime\\Tower of God": 4300739979,
|
||||||
|
"P:\\anime\\Prison School": 5023091161,
|
||||||
|
"P:\\anime\\Domestic Girlfriend": 4526369861,
|
||||||
|
"P:\\anime\\I Parry Everything": 4757235421,
|
||||||
|
"P:\\anime\\Classroom for Heroes": 4024333345,
|
||||||
|
"P:\\anime\\My Hero Academia - Vigilantes (2025)": 6139355726,
|
||||||
|
"P:\\anime\\The Iceblade Sorcerer Shall Rule the World": 4957596749,
|
||||||
|
"P:\\anime\\A Terrified Teacher at Ghoul School!": 7877236936,
|
||||||
|
"P:\\anime\\Deca-Dence": 5223908805,
|
||||||
|
"P:\\anime\\Otomege Sekai wa Mob ni Kibishii Sekai desu": 4212433019,
|
||||||
|
"P:\\anime\\Hai to Gensou no Grimgar": 2769261873,
|
||||||
|
"P:\\anime\\Black Clover": 9257210643,
|
||||||
|
"P:\\anime\\The World's Finest Assassin Gets Reincarnated in Another World as an Aristocrat (2021)": 13724318948,
|
||||||
|
"P:\\anime\\Mob Psycho 100": 15587250285,
|
||||||
|
"P:\\anime\\Mecha-Ude - Mechanical Arms": 7833270141,
|
||||||
|
"P:\\anime\\Moonrise (2025)": 7415752085,
|
||||||
|
"P:\\anime\\Blood Lad (2013)": 3963930670,
|
||||||
|
"P:\\anime\\From Old Country Bumpkin to Master Swordsman (2025)": 5043093837,
|
||||||
|
"P:\\anime\\Cowboy Bebop": 10008334150,
|
||||||
|
"P:\\anime\\Toilet-Bound Hanako-kun (2020)": 7614925159,
|
||||||
|
"P:\\anime\\The Familiar of Zero": 11204697509,
|
||||||
|
"P:\\anime\\Servamp (2016)": 3825335418,
|
||||||
|
"P:\\anime\\Mushoku Tensei - Jobless Reincarnation (2021)": 28463561868,
|
||||||
|
"P:\\anime\\Masou Gakuen HxH": 5217076051,
|
||||||
|
"P:\\anime\\Knight's & Magic": 5462539205,
|
||||||
|
"P:\\anime\\DAN DA DAN (2024)": 10293363650,
|
||||||
|
"P:\\anime\\Terror in Resonance (2014)": 2651080911,
|
||||||
|
"P:\\anime\\Masamune-kun no Revenge": 2189592644,
|
||||||
|
"P:\\anime\\Darwin's Game": 2896376206,
|
||||||
|
"P:\\anime\\Green Green": 4573681221,
|
||||||
|
"P:\\anime\\Heion Sedai no Idaten-tachi": 4801708412,
|
||||||
|
"P:\\anime\\The Reincarnation of the Strongest Exorcist in Another World": 4530803804,
|
||||||
|
"P:\\anime\\My Daughter Left the Nest and Returned an S-Rank Adventurer (2023)": 8845481436,
|
||||||
|
"P:\\anime\\Hamefura": 8824881383,
|
||||||
|
"P:\\anime\\Recovery of an MMO Junkie": 2534587997,
|
||||||
|
"P:\\anime\\Astra Lost in Space (2019)": 5522863567,
|
||||||
|
"P:\\anime\\Mobile Suit Gundam The Witch from Mercury": 4233451823,
|
||||||
|
"P:\\anime\\Unnamed Memory": 6391216954,
|
||||||
|
"P:\\anime\\My Home Hero": 10316487117,
|
||||||
|
"P:\\anime\\Noblesse": 3106598797,
|
||||||
|
"P:\\anime\\Quality Assurance in Another World": 6689588963,
|
||||||
|
"P:\\anime\\The Saint's Magic Power is Omnipotent": 2353633162,
|
||||||
|
"P:\\anime\\Gods' Games We Play": 9805921920,
|
||||||
|
"P:\\anime\\High School D\u00d7D (2012)": 138488392829,
|
||||||
|
"P:\\anime\\Buddy Daddies": 3249476770,
|
||||||
|
"P:\\anime\\The Testament of Sister New Devil": 9051073176,
|
||||||
|
"P:\\anime\\Why the Hell are You Here, Teacher!! (2019)": 1437748496,
|
||||||
|
"P:\\anime\\Grenadier": 4272486895,
|
||||||
|
"P:\\anime\\.deletedByTMM": 2941377788,
|
||||||
|
"P:\\anime\\Dragonar Academy": 5046527432,
|
||||||
|
"P:\\anime\\Genjitsu Shugi Yuusha no Oukoku Saikenki": 10459881239,
|
||||||
|
"P:\\anime\\Darling in the FranXX": 8035916582,
|
||||||
|
"P:\\anime\\Platinum End": 7525590740,
|
||||||
|
"P:\\anime\\Go! Go! Loser Ranger! (2024)": 8801862587,
|
||||||
|
"P:\\anime\\Mission - Yozakura Family": 9503269395,
|
||||||
|
"P:\\anime\\Attack on Titan": 49531319741,
|
||||||
|
"P:\\anime\\The Kings Avatar": 8090986074,
|
||||||
|
"P:\\anime\\Gokukoku no Brynhildr": 1836411221,
|
||||||
|
"P:\\anime\\Pok\u00e9mon (1997)": 64280309172,
|
||||||
|
"P:\\anime\\Trigun": 7302317846,
|
||||||
|
"P:\\anime\\Failure Frame - I Became the Strongest and Annihilated Everything with Low-Level Spells (2024)": 14994809254,
|
||||||
|
"P:\\anime\\The Daily Life of a Middle-Aged Online Shopper in Another World (2025)": 20550758199,
|
||||||
|
"P:\\anime\\Vermeil in Gold (2022)": 3029688342,
|
||||||
|
"P:\\anime\\A Returner's Magic Should Be Special": 4013798232,
|
||||||
|
"P:\\anime\\The Legendary Hero Is Dead! (2023)": 3859024813,
|
||||||
|
"P:\\anime\\Golden Boy": 1780893800,
|
||||||
|
"P:\\anime\\Tenchi Muyo! War on Geminar": 5177920884,
|
||||||
|
"P:\\anime\\Tokyo Ravens": 3797091570,
|
||||||
|
"P:\\anime\\Ore wo Suki nano wa Omae dake ka yo": 4930678131,
|
||||||
|
"P:\\anime\\Beast Tamer": 4948757269
|
||||||
|
}
|
||||||
2877
.cache/.cache_movies.json
Normal file
2877
.cache/.cache_movies.json
Normal file
File diff suppressed because it is too large
Load Diff
322
.cache/.cache_tv.json
Normal file
322
.cache/.cache_tv.json
Normal file
@ -0,0 +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\\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\\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\\Wolf Pack": 6844099384,
|
||||||
|
"P:\\tv\\WondLa": 1399628000,
|
||||||
|
"P:\\tv\\Worst Cooks in America (2010)": 22063867049,
|
||||||
|
"P:\\tv\\Yellowstone (2018)": 89724605866,
|
||||||
|
"P:\\tv\\Young Sheldon": 21714069112,
|
||||||
|
"P:\\tv\\Your Honor (2020)": 25879839349
|
||||||
|
}
|
||||||
3201
.folder_cache.json
Normal file
3201
.folder_cache.json
Normal file
File diff suppressed because it is too large
Load Diff
152
PROJECT_STRUCTURE.md
Normal file
152
PROJECT_STRUCTURE.md
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
# AV1 Batch Video Transcoder - Project Structure
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This project is a modular batch video transcoding system using NVIDIA's av1_nvenc codec with intelligent audio stream processing and resolution handling.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Entry Point
|
||||||
|
- **main.py** - CLI entry point with argument parsing
|
||||||
|
- Loads configuration from `config.xml`
|
||||||
|
- Initializes logger and CSV tracker
|
||||||
|
- Dispatches to `process_folder()` for batch processing
|
||||||
|
|
||||||
|
### Core Modules
|
||||||
|
|
||||||
|
#### `core/config_helper.py`
|
||||||
|
- XML configuration parser
|
||||||
|
- Returns dict with audio, encoding, and filter settings
|
||||||
|
- **Key Config:**
|
||||||
|
- `audio.stereo.high/medium`: Target bitrates for stereo audio (1080p/720p)
|
||||||
|
- `audio.multi_channel.low/medium`: Target bitrates for multichannel audio
|
||||||
|
- `encode.cq`: CQ values per content type (tv_720, tv_1080, movie_720, movie_1080, etc.)
|
||||||
|
- `encode.fallback`: Bitrate fallback settings (900k/1080p, 650k/720p, etc.)
|
||||||
|
- `extensions`: Video file types to process (mkv, mp4, etc.)
|
||||||
|
- `ignore_tags`: Files to skip (trailer, sample, etc.)
|
||||||
|
|
||||||
|
#### `core/logger_helper.py`
|
||||||
|
- Comprehensive logging to `logs/conversion.log`
|
||||||
|
- Captures source/target specs, audio decisions, bitrate info
|
||||||
|
- Separate handlers for console (INFO+) and file (DEBUG+)
|
||||||
|
|
||||||
|
#### `core/audio_handler.py`
|
||||||
|
- **`calculate_stream_bitrate(input_file, stream_index)`**: Extracts audio stream with ffmpeg `-c copy`, parses bitrate output, falls back to file size calculation
|
||||||
|
- **`get_audio_streams(input_file)`**: Detects all audio streams with robust bitrate calculation
|
||||||
|
- **`choose_audio_bitrate(channels, bitrate_kbps, audio_config, is_1080_class)`**: Returns (codec, target_bitrate) tuple
|
||||||
|
- Stereo 1080p+: >192k → encode; ≤192k → preserve ("copy")
|
||||||
|
- Stereo 720p: >160k → encode; ≤160k → preserve
|
||||||
|
- Multichannel: Encodes to low (384k) or medium (448k) based on current bitrate
|
||||||
|
|
||||||
|
#### `core/video_handler.py`
|
||||||
|
- **`get_source_resolution(input_file)`**: ffprobe detection of video dimensions
|
||||||
|
- **`determine_target_resolution(src_width, src_height, explicit_resolution)`**: Smart resolution logic
|
||||||
|
- If >1080p → scale to 1080p
|
||||||
|
- Else → preserve source resolution
|
||||||
|
- Can be overridden with explicit `--r 480/720/1080` argument
|
||||||
|
|
||||||
|
#### `core/encode_engine.py`
|
||||||
|
- **`run_ffmpeg(...)`**: Main FFmpeg encoding orchestration
|
||||||
|
- Builds command with av1_nvenc settings
|
||||||
|
- Per-stream audio codec/bitrate decisions
|
||||||
|
- Handles both CQ and Bitrate modes
|
||||||
|
- Logs detailed before/after specs
|
||||||
|
|
||||||
|
#### `core/process_manager.py`
|
||||||
|
- **`process_folder(...)`**: Main batch processing loop
|
||||||
|
- Classifies files as TV/Movie/Anime based on path
|
||||||
|
- Detects per-file source resolution
|
||||||
|
- Applies smart resolution defaults or explicit overrides
|
||||||
|
- Handles CQ → Bitrate fallback if size threshold exceeded
|
||||||
|
- Tracks results in `conversion_tracker.csv`
|
||||||
|
- Deletes originals after successful encoding
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **User** runs: `python main.py "C:\Videos\TV\ShowName" --r 720 --m bitrate`
|
||||||
|
2. **main.py** parses args, loads config.xml
|
||||||
|
3. **process_manager** iterates video files in folder
|
||||||
|
4. For each file:
|
||||||
|
- **video_handler** detects source resolution
|
||||||
|
- **audio_handler** analyzes audio streams and calculates bitrates
|
||||||
|
- **encode_engine** builds FFmpeg command with smart audio/resolution settings
|
||||||
|
- FFmpeg encodes with per-stream audio decisions
|
||||||
|
- **tracker** logs results to CSV
|
||||||
|
5. **logger** captures all details to `logs/conversion.log`
|
||||||
|
|
||||||
|
## Configuration Examples
|
||||||
|
|
||||||
|
### Force 720p Bitrate Mode
|
||||||
|
```bash
|
||||||
|
python main.py "C:\Videos\TV\Show" --r 720 --m bitrate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Force 1080p with CQ=28
|
||||||
|
```bash
|
||||||
|
python main.py "C:\Videos\Movies" --cq 28 --r 1080
|
||||||
|
```
|
||||||
|
|
||||||
|
### Smart Mode (preserve resolution, 4K→1080p)
|
||||||
|
```bash
|
||||||
|
python main.py "C:\Videos\Mixed"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Force 480p (for low-res content)
|
||||||
|
```bash
|
||||||
|
python main.py "C:\Videos\OldTV" --r 480
|
||||||
|
```
|
||||||
|
|
||||||
|
## Audio Encoding Logic
|
||||||
|
|
||||||
|
### Decision Tree
|
||||||
|
```
|
||||||
|
Stereo audio?
|
||||||
|
├─ YES + 1080p: [>192kbps] ENCODE to 192k AAC, [≤192k] COPY
|
||||||
|
├─ YES + 720p: [>160kbps] ENCODE to 160k AAC, [≤160k] COPY
|
||||||
|
└─ NO (Multichannel 6ch+): ENCODE to 384k (low) or 448k (medium) AAC
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rationale
|
||||||
|
- Preserves high-quality original audio when already well-compressed
|
||||||
|
- Re-encodes excessive bitrate audio to standard targets
|
||||||
|
- Handles stereo at different resolutions appropriately
|
||||||
|
- Normalizes multichannel to 6ch surround (5.1) or 2ch stereo
|
||||||
|
|
||||||
|
## Files Modified/Created
|
||||||
|
|
||||||
|
### New Modules (Session Work)
|
||||||
|
- ✅ `core/audio_handler.py` - NEW
|
||||||
|
- ✅ `core/video_handler.py` - NEW
|
||||||
|
- ✅ `core/encode_engine.py` - NEW
|
||||||
|
- ✅ `core/process_manager.py` - NEW
|
||||||
|
- ✅ `main.py` - REFACTORED (524 lines → 70 lines)
|
||||||
|
|
||||||
|
### Cleanup
|
||||||
|
- ✅ Deleted `core/ffmpeg_helper.py` (code moved to audio_handler)
|
||||||
|
- ✅ Deleted `core/process_helper.py` (empty)
|
||||||
|
- ✅ Deleted `core/tracker_helper.py` (empty)
|
||||||
|
|
||||||
|
### Enhanced
|
||||||
|
- ✅ `config.xml` - Added `<low>384000</low>` to multi_channel audio
|
||||||
|
- ✅ `transcode.bat` - Enhanced with job counting and status tracking
|
||||||
|
- ✅ `paths.txt` - Queue format with --r and --m flags
|
||||||
|
|
||||||
|
## Validation Checklist
|
||||||
|
- ✅ All modules pass Pylance syntax check
|
||||||
|
- ✅ All imports resolve correctly
|
||||||
|
- ✅ Config loads and provides expected keys
|
||||||
|
- ✅ No unused/deprecated files remain
|
||||||
|
- ✅ Project structure clean and maintainable
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
Verify the complete system:
|
||||||
|
```bash
|
||||||
|
# Test imports
|
||||||
|
python -c "from core.audio_handler import *; from core.video_handler import *; print('OK')"
|
||||||
|
|
||||||
|
# Run on test folder
|
||||||
|
python main.py "C:\Test\Videos" --r 720 --m bitrate
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
cat logs/conversion.log
|
||||||
|
```
|
||||||
@ -9,7 +9,7 @@
|
|||||||
<processing_folder>processing</processing_folder>
|
<processing_folder>processing</processing_folder>
|
||||||
|
|
||||||
<!-- File suffix added to encoded outputs -->
|
<!-- File suffix added to encoded outputs -->
|
||||||
<suffix> -EHX</suffix>
|
<suffix> - [EHX]</suffix>
|
||||||
|
|
||||||
<!-- Allowed input extensions -->
|
<!-- Allowed input extensions -->
|
||||||
<extensions>.mkv,.mp4</extensions>
|
<extensions>.mkv,.mp4</extensions>
|
||||||
@ -18,7 +18,7 @@
|
|||||||
<ignore_tags>ehx</ignore_tags> <!-- ,megusta -->
|
<ignore_tags>ehx</ignore_tags> <!-- ,megusta -->
|
||||||
|
|
||||||
<!-- Reduction ratio threshold: output must be <= this % of original or encoding fails -->
|
<!-- Reduction ratio threshold: output must be <= this % of original or encoding fails -->
|
||||||
<reduction_ratio_threshold>0.7</reduction_ratio_threshold>
|
<reduction_ratio_threshold>0.75</reduction_ratio_threshold>
|
||||||
</general>
|
</general>
|
||||||
|
|
||||||
<!-- =============================
|
<!-- =============================
|
||||||
@ -27,6 +27,7 @@
|
|||||||
<path_mappings>
|
<path_mappings>
|
||||||
<map from="P:\tv" to="/mnt/plex/tv" />
|
<map from="P:\tv" to="/mnt/plex/tv" />
|
||||||
<map from="P:\anime" to="/mnt/plex/anime" />
|
<map from="P:\anime" to="/mnt/plex/anime" />
|
||||||
|
<map from="P:\movies" to="/mnt/plex/movies" />
|
||||||
</path_mappings>
|
</path_mappings>
|
||||||
|
|
||||||
<!-- =============================
|
<!-- =============================
|
||||||
|
|||||||
@ -227,3 +227,13 @@ tv,Canada's Drag Race vs The World,Canada's Drag Race - Canada vs. The World - S
|
|||||||
tv,Canada's Drag Race vs The World,Canada's Drag Race - Canada vs. The World - S01E05 - Spy Queens x264 AAC WEBDL-1080p SLAG -EHX.mkv,2576.93,641.31,24.9,Bitrate
|
tv,Canada's Drag Race vs The World,Canada's Drag Race - Canada vs. The World - S01E05 - Spy Queens x264 AAC WEBDL-1080p SLAG -EHX.mkv,2576.93,641.31,24.9,Bitrate
|
||||||
tv,Canada's Drag Race vs The World,Canada's Drag Race - Canada vs. The World - S01E06 - Grand Finale x264 AAC WEBDL-1080p SLAG -EHX.mkv,2575.57,640.14,24.9,Bitrate
|
tv,Canada's Drag Race vs The World,Canada's Drag Race - Canada vs. The World - S01E06 - Grand Finale x264 AAC WEBDL-1080p SLAG -EHX.mkv,2575.57,640.14,24.9,Bitrate
|
||||||
movie,N/A,2025-12-29 21-53-23 -EHX.mp4,343.3,47.9,14.0,CQ
|
movie,N/A,2025-12-29 21-53-23 -EHX.mp4,343.3,47.9,14.0,CQ
|
||||||
|
movie,N/A,How the Grinch Stole Christmas (2000) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv,5626.25,3268.49,58.1,CQ
|
||||||
|
movie,N/A,Oppenheimer (2023) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv,9344.87,3040.3,32.5,1920x1080,1920x1080,1,32,CQ
|
||||||
|
movie,N/A,Pacific Rim (2013) x265 AAC 7.1 Bluray-1080p Tigole -EHX.mkv,5624.98,4029.53,71.6,1920x1080,1920x1080,2,32,CQ
|
||||||
|
movie,N/A,"Planes, Trains and Automobiles (1987) x265 AAC 5.1 Bluray-1080p afm72 -EHX.mkv",4489.88,2513.95,56.0,1920x1080,1920x1080,1,32,CQ
|
||||||
|
movie,N/A,Hackers (1995) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv,5601.42,2346.49,41.9,1920x804,1920x804,2,32,CQ
|
||||||
|
movie,N/A,Bullet Train (2022) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv,5857.37,2444.68,41.7,1920x804,1920x804,2,32,CQ
|
||||||
|
movie,N/A,The Truman Show (1998) x265 AAC 5.1 Bluray-1080p Silence -EHX.mkv,5152.45,2971.12,57.7,1918x1080,1918x1080,1,32,CQ
|
||||||
|
movie,N/A,John Wick - Chapter 4 (2023) x265 AAC 7.1 Bluray-1080p Tigole -EHX.mkv,8144.41,3288.02,40.4,1920x804,1920x804,1,32,CQ
|
||||||
|
movie,N/A,F1 (2025) x265 EAC3 7.1 Bluray-1080p SAMPA -EHX.mkv,7923.16,4044.9,51.1,1920x1080,1920x1080,1,32,CQ
|
||||||
|
movie,N/A,Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigole - [EHX].mkv,6522.97,4495.6,68.9,1920x1040,1920x1040,4,32,CQ
|
||||||
|
|||||||
|
Can't render this file because it has a wrong number of fields in line 14.
|
174
core/audio_handler.py
Normal file
174
core/audio_handler.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
# core/audio_handler.py
|
||||||
|
"""Audio stream detection, bitrate calculation, and codec selection."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from core.logger_helper import setup_logger
|
||||||
|
|
||||||
|
logger = setup_logger(Path(__file__).parent.parent / "logs")
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_stream_bitrate(input_file: Path, stream_index: int) -> int:
|
||||||
|
"""
|
||||||
|
Extract audio stream to temporary file using -c copy, capture bitrate from ffmpeg output.
|
||||||
|
Returns bitrate in kbps. Falls back to 0 (and uses metadata) if extraction fails.
|
||||||
|
|
||||||
|
Uses ffmpeg's reported bitrate which is more accurate than calculating from file size/duration.
|
||||||
|
"""
|
||||||
|
temp_fd, temp_audio_path = tempfile.mkstemp(suffix=".aac", dir=None)
|
||||||
|
os.close(temp_fd)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Step 1: Extract audio stream with -c copy (lossless extraction)
|
||||||
|
# ffmpeg outputs bitrate info to stderr
|
||||||
|
extract_cmd = [
|
||||||
|
"ffmpeg", "-y", "-i", str(input_file),
|
||||||
|
"-map", f"0:a:{stream_index}",
|
||||||
|
"-c", "copy",
|
||||||
|
temp_audio_path
|
||||||
|
]
|
||||||
|
logger.debug(f"Extracting audio stream {stream_index} to temporary file for bitrate calculation...")
|
||||||
|
result = subprocess.run(extract_cmd, capture_output=True, text=True, check=True)
|
||||||
|
|
||||||
|
# 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"):
|
||||||
|
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=")
|
||||||
|
if len(parts) > 1:
|
||||||
|
bitrate_str = parts[1].strip().split("kbits/s")[0].strip()
|
||||||
|
try:
|
||||||
|
bitrate_kbps = int(float(bitrate_str))
|
||||||
|
logger.debug(f"Stream {stream_index}: Extracted bitrate from ffmpeg output: {bitrate_kbps} kbps")
|
||||||
|
break
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If we couldn't parse bitrate from output, fall back to calculation
|
||||||
|
if bitrate_kbps == 0:
|
||||||
|
logger.debug(f"Stream {stream_index}: Could not parse bitrate from ffmpeg output, calculating from file size...")
|
||||||
|
file_size_bytes = os.path.getsize(temp_audio_path)
|
||||||
|
|
||||||
|
# Get duration using ffprobe
|
||||||
|
duration_cmd = [
|
||||||
|
"ffprobe", "-v", "error",
|
||||||
|
"-show_entries", "format=duration",
|
||||||
|
"-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")
|
||||||
|
|
||||||
|
return bitrate_kbps
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to calculate bitrate for stream {stream_index}: {e}. Will fall back to metadata.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up temporary audio file
|
||||||
|
try:
|
||||||
|
if os.path.exists(temp_audio_path):
|
||||||
|
os.remove(temp_audio_path)
|
||||||
|
logger.debug(f"Deleted temporary audio file: {temp_audio_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not delete temporary file {temp_audio_path}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
"""
|
||||||
|
cmd = [
|
||||||
|
"ffprobe","-v","error","-select_streams","a",
|
||||||
|
"-show_entries","stream=index,channels,bit_rate,tags=language",
|
||||||
|
"-of","json", str(input_file)
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
data = json.loads(result.stdout)
|
||||||
|
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")
|
||||||
|
bit_rate_meta = int(s.get("bit_rate", 0)) if s.get("bit_rate") else 0
|
||||||
|
|
||||||
|
# Calculate robust bitrate by extracting the audio stream
|
||||||
|
calculated_bitrate_kbps = calculate_stream_bitrate(input_file, stream_num)
|
||||||
|
|
||||||
|
# If calculation failed, fall back to metadata
|
||||||
|
if calculated_bitrate_kbps == 0:
|
||||||
|
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))
|
||||||
|
|
||||||
|
return streams
|
||||||
|
|
||||||
|
|
||||||
|
def choose_audio_bitrate(channels: int, bitrate_kbps: int, audio_config: dict, is_1080_class: bool) -> tuple:
|
||||||
|
"""
|
||||||
|
Choose audio codec and bitrate based on channel count, detected bitrate, and resolution.
|
||||||
|
|
||||||
|
Returns tuple: (codec, target_bitrate_bps)
|
||||||
|
- codec: "aac", "libopus", or "copy" (to preserve original without re-encoding)
|
||||||
|
- target_bitrate_bps: target bitrate in bits/sec (0 if using "copy")
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
Stereo + 1080p:
|
||||||
|
- Above 192k → high (192k) with AAC
|
||||||
|
- At/below 192k → preserve (copy)
|
||||||
|
|
||||||
|
Stereo + 720p:
|
||||||
|
- Above 160k → medium (160k) with AAC
|
||||||
|
- At/below 160k → preserve (copy)
|
||||||
|
|
||||||
|
Multi-channel:
|
||||||
|
- Below 384k → low (384k) with AAC
|
||||||
|
- 384k to below medium → low (384k) with AAC
|
||||||
|
- Medium and above → medium with AAC
|
||||||
|
"""
|
||||||
|
# Normalize to 2ch or 6ch output
|
||||||
|
output_channels = 6 if channels >= 6 else 2
|
||||||
|
|
||||||
|
if output_channels == 2:
|
||||||
|
# Stereo logic
|
||||||
|
if is_1080_class:
|
||||||
|
# 1080p+ stereo
|
||||||
|
high_br = audio_config["stereo"]["high"]
|
||||||
|
if bitrate_kbps > (high_br / 1000): # Above 192k
|
||||||
|
return ("aac", high_br)
|
||||||
|
else:
|
||||||
|
# Preserve original
|
||||||
|
return ("copy", 0)
|
||||||
|
else:
|
||||||
|
# 720p stereo
|
||||||
|
medium_br = audio_config["stereo"]["medium"]
|
||||||
|
if bitrate_kbps > (medium_br / 1000): # Above 160k
|
||||||
|
return ("aac", medium_br)
|
||||||
|
else:
|
||||||
|
# Preserve original
|
||||||
|
return ("copy", 0)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Multi-channel (6ch+) logic
|
||||||
|
low_br = audio_config["multi_channel"]["low"]
|
||||||
|
medium_br = audio_config["multi_channel"]["medium"]
|
||||||
|
|
||||||
|
if bitrate_kbps < (medium_br / 1000):
|
||||||
|
# Below medium, use low
|
||||||
|
return ("aac", low_br)
|
||||||
|
else:
|
||||||
|
# Medium and above, use medium
|
||||||
|
return ("aac", medium_br)
|
||||||
117
core/encode_engine.py
Normal file
117
core/encode_engine.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
# core/encode_engine.py
|
||||||
|
"""FFmpeg encoding engine with comprehensive logging."""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from core.audio_handler import get_audio_streams, choose_audio_bitrate
|
||||||
|
from core.logger_helper import setup_logger
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
Run FFmpeg encode with comprehensive logging.
|
||||||
|
|
||||||
|
Returns tuple: (orig_size, out_size, reduction_ratio)
|
||||||
|
"""
|
||||||
|
streams = get_audio_streams(input_file)
|
||||||
|
|
||||||
|
# Log comprehensive encode settings
|
||||||
|
header = f"\n🧩 ENCODE SETTINGS"
|
||||||
|
logger.info(header)
|
||||||
|
print(" ")
|
||||||
|
|
||||||
|
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" • Scale Filter: {filter_flags}")
|
||||||
|
logger.info(f" • Encode Method: {method}")
|
||||||
|
if method == "CQ":
|
||||||
|
logger.info(f" • CQ Value: {cq}")
|
||||||
|
else:
|
||||||
|
res_key = "1080" if scale_height >= 1080 or scale_width >= 1920 else "720"
|
||||||
|
vb = bitrate_config.get(f"bitrate_{res_key}", "900k")
|
||||||
|
maxrate = bitrate_config.get(f"maxrate_{res_key}", "1250k")
|
||||||
|
logger.info(f" • Bitrate: {vb}, Max: {maxrate}")
|
||||||
|
|
||||||
|
logger.info(f" Audio Streams ({len(streams)} detected):")
|
||||||
|
print(" ")
|
||||||
|
|
||||||
|
for (index, channels, avg_bitrate, src_lang, meta_bitrate) 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
|
||||||
|
codec, br = choose_audio_bitrate(output_channels, avg_bitrate, audio_config, is_1080_class)
|
||||||
|
|
||||||
|
if codec == "copy":
|
||||||
|
action = "COPY (preserve)"
|
||||||
|
bitrate_display = f"{avg_bitrate}kbps"
|
||||||
|
else:
|
||||||
|
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}"
|
||||||
|
print(line)
|
||||||
|
logger.info(line)
|
||||||
|
|
||||||
|
cmd = ["ffmpeg","-y","-i",str(input_file),
|
||||||
|
"-vf",f"scale={scale_width}:{scale_height}:flags={filter_flags}:force_original_aspect_ratio=decrease",
|
||||||
|
"-map","0:v","-map","0:a","-map","0:s?",
|
||||||
|
"-c:v","av1_nvenc","-preset","p1","-pix_fmt","p010le"]
|
||||||
|
|
||||||
|
if method=="CQ":
|
||||||
|
cmd += ["-cq", str(cq)]
|
||||||
|
else:
|
||||||
|
# Use bitrate config (fallback mode)
|
||||||
|
res_key = "1080" if scale_height >= 1080 or scale_width >= 1920 else "720"
|
||||||
|
vb = bitrate_config.get(f"bitrate_{res_key}", "900k")
|
||||||
|
maxrate = bitrate_config.get(f"maxrate_{res_key}", "1250k")
|
||||||
|
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):
|
||||||
|
# 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
|
||||||
|
codec, br = choose_audio_bitrate(output_channels, avg_bitrate, audio_config, is_1080_class)
|
||||||
|
|
||||||
|
if codec == "copy":
|
||||||
|
# Preserve original audio
|
||||||
|
cmd += [f"-c:a:{i}", "copy"]
|
||||||
|
else:
|
||||||
|
# Re-encode with target bitrate
|
||||||
|
cmd += [
|
||||||
|
f"-c:a:{i}", codec,
|
||||||
|
f"-b:a:{i}", str(br),
|
||||||
|
f"-ac:{i}", str(output_channels),
|
||||||
|
f"-channel_layout:a:{i}", "5.1" if output_channels == 6 else "stereo"
|
||||||
|
]
|
||||||
|
|
||||||
|
cmd += ["-c:s","copy",str(output_file)]
|
||||||
|
|
||||||
|
print(f"\n🎬 Running {method} encode: {output_file.name}")
|
||||||
|
logger.info(f"Running {method} encode: {output_file.name}")
|
||||||
|
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
orig_size = input_file.stat().st_size
|
||||||
|
out_size = output_file.stat().st_size
|
||||||
|
reduction_ratio = out_size / orig_size
|
||||||
|
|
||||||
|
# Log comprehensive results
|
||||||
|
logger.info(f"\n📊 ENCODE RESULTS:")
|
||||||
|
logger.info(f" Original Size: {orig_size/1e6:.2f} MB")
|
||||||
|
logger.info(f" Encoded Size: {out_size/1e6:.2f} MB")
|
||||||
|
logger.info(f" Reduction: {reduction_ratio:.1%} of original ({(1-reduction_ratio):.1%} saved)")
|
||||||
|
logger.info(f" Resolution: {src_width}x{src_height} → {scale_width}x{scale_height}")
|
||||||
|
logger.info(f" Audio Streams: {len(streams)} streams processed")
|
||||||
|
|
||||||
|
msg = f"📦 Original: {orig_size/1e6:.2f} MB → Encoded: {out_size/1e6:.2f} MB ({reduction_ratio:.1%} of original)"
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
return orig_size, out_size, reduction_ratio
|
||||||
@ -1,207 +0,0 @@
|
|||||||
# core/ffmpeg_helper.py
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
from core.logger_helper import setup_logger
|
|
||||||
|
|
||||||
logger = setup_logger(Path(__file__).parent.parent / "logs")
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# ROBUST BITRATE CALCULATION
|
|
||||||
# =============================
|
|
||||||
def calculate_stream_bitrate(input_file: Path, stream_index: int) -> int:
|
|
||||||
"""
|
|
||||||
Extract audio stream to temporary file using -c copy, calculate bitrate from file size and duration.
|
|
||||||
Returns bitrate in kbps.
|
|
||||||
|
|
||||||
Formula: bitrate_kbps = (file_size_bytes * 8) / duration_seconds / 1000
|
|
||||||
"""
|
|
||||||
temp_fd, temp_audio_path = tempfile.mkstemp(suffix=".aac", dir=None)
|
|
||||||
os.close(temp_fd)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Step 1: Extract audio stream with -c copy (lossless extraction)
|
|
||||||
extract_cmd = [
|
|
||||||
"ffmpeg", "-y", "-i", str(input_file),
|
|
||||||
"-map", f"0:a:{stream_index}",
|
|
||||||
"-c", "copy",
|
|
||||||
temp_audio_path
|
|
||||||
]
|
|
||||||
logger.debug(f"Extracting audio stream {stream_index} to temporary file...")
|
|
||||||
subprocess.run(extract_cmd, capture_output=True, text=True, check=True)
|
|
||||||
|
|
||||||
# Step 2: Get duration using ffprobe
|
|
||||||
duration_cmd = [
|
|
||||||
"ffprobe", "-v", "error",
|
|
||||||
"-show_entries", "format=duration",
|
|
||||||
"-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())
|
|
||||||
|
|
||||||
# Step 3: Get file size and calculate bitrate
|
|
||||||
file_size_bytes = os.path.getsize(temp_audio_path)
|
|
||||||
bitrate_kbps = int((file_size_bytes * 8) / duration_seconds / 1000)
|
|
||||||
|
|
||||||
logger.debug(f"Stream {stream_index}: size={file_size_bytes} bytes, duration={duration_seconds:.2f}s, calculated_bitrate={bitrate_kbps} kbps")
|
|
||||||
return bitrate_kbps
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Failed to calculate bitrate for stream {stream_index}: {e}. Falling back to metadata.")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
finally:
|
|
||||||
# Clean up temporary audio file
|
|
||||||
try:
|
|
||||||
if os.path.exists(temp_audio_path):
|
|
||||||
os.remove(temp_audio_path)
|
|
||||||
logger.debug(f"Deleted temporary audio file: {temp_audio_path}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Could not delete temporary file {temp_audio_path}: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def get_audio_streams(input_file: Path):
|
|
||||||
"""Return a list of (index, channels, bitrate_kbps, lang)
|
|
||||||
|
|
||||||
Uses robust bitrate calculation by extracting each stream and computing
|
|
||||||
bitrate from file size and duration instead of relying on metadata.
|
|
||||||
"""
|
|
||||||
cmd = [
|
|
||||||
"ffprobe", "-v", "error",
|
|
||||||
"-select_streams", "a",
|
|
||||||
"-show_entries", "stream=index,channels,bit_rate,tags=language",
|
|
||||||
"-of", "json", str(input_file)
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
data = json.loads(result.stdout or "{}")
|
|
||||||
streams = []
|
|
||||||
|
|
||||||
for i, s in enumerate(data.get("streams", [])):
|
|
||||||
index = s["index"]
|
|
||||||
channels = s.get("channels", 2)
|
|
||||||
lang = s.get("tags", {}).get("language", "und")
|
|
||||||
|
|
||||||
# Calculate robust bitrate from extraction
|
|
||||||
calculated_bitrate = calculate_stream_bitrate(input_file, i)
|
|
||||||
|
|
||||||
# Fallback to metadata if calculation fails
|
|
||||||
if calculated_bitrate == 0:
|
|
||||||
bitrate = int(int(s.get("bit_rate", 128000)) / 1000)
|
|
||||||
logger.info(f"Stream {index}: Using metadata bitrate {bitrate} kbps (calculation failed)")
|
|
||||||
else:
|
|
||||||
bitrate = calculated_bitrate
|
|
||||||
logger.info(f"Stream {index}: Using calculated bitrate {bitrate} kbps")
|
|
||||||
|
|
||||||
streams.append((index, channels, bitrate, lang))
|
|
||||||
|
|
||||||
return streams
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# AUDIO DECISION LOGIC
|
|
||||||
# =============================
|
|
||||||
def choose_audio_settings(channels: int, bitrate_kbps: int, audio_config: dict) -> Tuple[str, int]:
|
|
||||||
"""
|
|
||||||
Return (codec, target_bitrate)
|
|
||||||
Rules:
|
|
||||||
- If 128 kbps or lower → use Opus
|
|
||||||
- Otherwise → use AAC
|
|
||||||
- Use audio_config to bucket bitrates.
|
|
||||||
"""
|
|
||||||
if channels == 2:
|
|
||||||
if bitrate_kbps <= 80:
|
|
||||||
target_br = audio_config["stereo"]["low"]
|
|
||||||
elif bitrate_kbps <= 112:
|
|
||||||
target_br = audio_config["stereo"]["medium"]
|
|
||||||
else:
|
|
||||||
target_br = audio_config["stereo"]["high"]
|
|
||||||
else:
|
|
||||||
if bitrate_kbps <= 176:
|
|
||||||
target_br = audio_config["multi_channel"]["low"]
|
|
||||||
else:
|
|
||||||
target_br = audio_config["multi_channel"]["high"]
|
|
||||||
|
|
||||||
# Opus threshold: <=128 kbps
|
|
||||||
threshold = audio_config.get("use_opus_below_kbps", 128)
|
|
||||||
codec = "libopus" if target_br <= threshold * 1000 else "aac"
|
|
||||||
return codec, target_br
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# FFMPEG COMMAND BUILDER
|
|
||||||
# =============================
|
|
||||||
def build_ffmpeg_command(input_file: Path, output_file: Path,
|
|
||||||
cq: int, width: int, height: int,
|
|
||||||
filter_flags: str, audio_config: dict):
|
|
||||||
"""Builds FFmpeg command with smart audio logic."""
|
|
||||||
streams = get_audio_streams(input_file)
|
|
||||||
|
|
||||||
logger.info(f"🎛 Detected {len(streams)} audio stream(s). Building command...")
|
|
||||||
cmd = [
|
|
||||||
"ffmpeg", "-y", "-i", str(input_file),
|
|
||||||
"-vf", f"scale={width}:{height}:flags={filter_flags}:force_original_aspect_ratio=decrease",
|
|
||||||
"-map", "0:v", "-map", "0:a", "-map", "0:s?",
|
|
||||||
"-c:v", "av1_nvenc", "-preset", "p1", "-cq", str(cq),
|
|
||||||
"-pix_fmt", "p010le"
|
|
||||||
]
|
|
||||||
|
|
||||||
for i, (index, channels, bitrate, lang) in enumerate(streams):
|
|
||||||
codec, br = choose_audio_settings(channels, bitrate, audio_config)
|
|
||||||
cmd += [
|
|
||||||
f"-c:a:{i}", codec,
|
|
||||||
f"-b:a:{i}", str(br),
|
|
||||||
f"-ac:{i}", str(channels),
|
|
||||||
f"-metadata:s:a:{i}", f"language={lang}"
|
|
||||||
]
|
|
||||||
|
|
||||||
cmd += ["-c:s", "copy", str(output_file)]
|
|
||||||
return cmd, streams
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# ENCODE RUNNER
|
|
||||||
# =============================
|
|
||||||
def run_encode(input_file: Path, output_file: Path, cq: int,
|
|
||||||
width: int, height: int, filter_flags: str,
|
|
||||||
audio_config: dict):
|
|
||||||
"""Handles encode, fallback logic, and returns size stats."""
|
|
||||||
cmd, streams = build_ffmpeg_command(input_file, output_file, cq, width, height, filter_flags, audio_config)
|
|
||||||
logger.info(f"🎬 Running FFmpeg CQ encode → {output_file.name}")
|
|
||||||
subprocess.run(cmd, check=True)
|
|
||||||
|
|
||||||
# Size check
|
|
||||||
orig_size = input_file.stat().st_size
|
|
||||||
out_size = output_file.stat().st_size
|
|
||||||
ratio = out_size / orig_size
|
|
||||||
logger.info(f"📦 Size: {orig_size/1e6:.2f}MB → {out_size/1e6:.2f}MB ({ratio:.1%})")
|
|
||||||
|
|
||||||
# Fallback logic
|
|
||||||
if ratio >= 0.5:
|
|
||||||
logger.warning(f"⚠️ Reduction too low ({ratio:.0%}), retrying with bitrate mode...")
|
|
||||||
output_file.unlink(missing_ok=True)
|
|
||||||
vb, maxrate, bufsize = (
|
|
||||||
("1500k", "1750k", "2250k") if height >= 1080
|
|
||||||
else ("900k", "1250k", "1600k")
|
|
||||||
)
|
|
||||||
cmd = [
|
|
||||||
"ffmpeg", "-y", "-i", str(input_file),
|
|
||||||
"-vf", f"scale={width}:{height}:flags={filter_flags}:force_original_aspect_ratio=decrease",
|
|
||||||
"-map", "0:v", "-map", "0:a", "-map", "0:s?",
|
|
||||||
"-c:v", "av1_nvenc", "-preset", "p1",
|
|
||||||
"-b:v", vb, "-maxrate", maxrate, "-bufsize", bufsize,
|
|
||||||
"-pix_fmt", "p010le"
|
|
||||||
]
|
|
||||||
for i, (index, channels, bitrate, lang) in enumerate(streams):
|
|
||||||
codec, br = choose_audio_settings(channels, bitrate, audio_config)
|
|
||||||
cmd += [
|
|
||||||
f"-c:a:{i}", codec,
|
|
||||||
f"-b:a:{i}", str(br),
|
|
||||||
f"-ac:{i}", str(channels),
|
|
||||||
f"-metadata:s:a:{i}", f"language={lang}"
|
|
||||||
]
|
|
||||||
cmd += ["-c:s", "copy", str(output_file)]
|
|
||||||
subprocess.run(cmd, check=True)
|
|
||||||
|
|
||||||
return orig_size, out_size
|
|
||||||
226
core/hardware_helper.py
Normal file
226
core/hardware_helper.py
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Hardware Detection and Optimization Module
|
||||||
|
Detects available hardware (GPU, CPU) and recommends optimal encoding settings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import platform
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from .logger_helper import setup_logger
|
||||||
|
|
||||||
|
logger = setup_logger(Path(__file__).parent.parent / "logs")
|
||||||
|
|
||||||
|
|
||||||
|
class HardwareInfo:
|
||||||
|
"""Detects and stores hardware capabilities."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.platform_name = platform.system() # Windows, Linux, Darwin
|
||||||
|
self.cpu_cores = self._get_cpu_cores()
|
||||||
|
self.gpu_type = self._detect_gpu()
|
||||||
|
self.available_encoders = self._check_ffmpeg_encoders()
|
||||||
|
self.recommended_encoder = self._recommend_encoder()
|
||||||
|
self.recommended_settings = self._get_recommended_settings()
|
||||||
|
|
||||||
|
def _get_cpu_cores(self):
|
||||||
|
"""Get number of CPU cores."""
|
||||||
|
import multiprocessing
|
||||||
|
return multiprocessing.cpu_count()
|
||||||
|
|
||||||
|
def _detect_gpu(self):
|
||||||
|
"""Detect GPU type (NVIDIA, AMD, Intel, None)."""
|
||||||
|
if self.platform_name == "Windows":
|
||||||
|
return self._detect_gpu_windows()
|
||||||
|
elif self.platform_name == "Linux":
|
||||||
|
return self._detect_gpu_linux()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _detect_gpu_windows(self):
|
||||||
|
"""Detect GPU on Windows using DXDIAG or WMI."""
|
||||||
|
try:
|
||||||
|
# Try using wmic (Windows only)
|
||||||
|
result = subprocess.run(
|
||||||
|
["wmic", "path", "win32_videocontroller", "get", "name"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
gpu_info = result.stdout.lower()
|
||||||
|
|
||||||
|
if "nvidia" in gpu_info or "geforce" in gpu_info or "quadro" in gpu_info:
|
||||||
|
return "nvidia"
|
||||||
|
elif "amd" in gpu_info or "radeon" in gpu_info:
|
||||||
|
return "amd"
|
||||||
|
elif "intel" in gpu_info:
|
||||||
|
return "intel"
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not detect GPU via WMI: {e}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _detect_gpu_linux(self):
|
||||||
|
"""Detect GPU on Linux using lspci."""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["lspci"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
gpu_info = result.stdout.lower()
|
||||||
|
|
||||||
|
if "nvidia" in gpu_info:
|
||||||
|
return "nvidia"
|
||||||
|
elif "amd" in gpu_info:
|
||||||
|
return "amd"
|
||||||
|
elif "intel" in gpu_info:
|
||||||
|
return "intel"
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not detect GPU via lspci: {e}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _check_ffmpeg_encoders(self):
|
||||||
|
"""Check available encoders in FFmpeg."""
|
||||||
|
encoders = {
|
||||||
|
"h264_nvenc": False,
|
||||||
|
"hevc_nvenc": False,
|
||||||
|
"h264_amf": False,
|
||||||
|
"hevc_amf": False,
|
||||||
|
"h264_qsv": False,
|
||||||
|
"hevc_qsv": False,
|
||||||
|
"libx264": False,
|
||||||
|
"libx265": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["ffmpeg", "-encoders"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
output = result.stdout.lower()
|
||||||
|
|
||||||
|
for encoder in encoders:
|
||||||
|
if encoder in output:
|
||||||
|
encoders[encoder] = True
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not check FFmpeg encoders: {e}")
|
||||||
|
|
||||||
|
return encoders
|
||||||
|
|
||||||
|
def _recommend_encoder(self):
|
||||||
|
"""Recommend best encoder based on hardware."""
|
||||||
|
# Prefer NVIDIA NVENC > AMD AMF > Intel QSV > CPU
|
||||||
|
|
||||||
|
if self.gpu_type == "nvidia":
|
||||||
|
if self.available_encoders.get("hevc_nvenc"):
|
||||||
|
return "hevc_nvenc" # H.265 on NVIDIA is efficient
|
||||||
|
elif self.available_encoders.get("h264_nvenc"):
|
||||||
|
return "h264_nvenc"
|
||||||
|
|
||||||
|
if self.gpu_type == "amd":
|
||||||
|
if self.available_encoders.get("hevc_amf"):
|
||||||
|
return "hevc_amf"
|
||||||
|
elif self.available_encoders.get("h264_amf"):
|
||||||
|
return "h264_amf"
|
||||||
|
|
||||||
|
if self.gpu_type == "intel":
|
||||||
|
if self.available_encoders.get("hevc_qsv"):
|
||||||
|
return "hevc_qsv"
|
||||||
|
elif self.available_encoders.get("h264_qsv"):
|
||||||
|
return "h264_qsv"
|
||||||
|
|
||||||
|
# Fallback to CPU encoders
|
||||||
|
if self.available_encoders.get("libx265"):
|
||||||
|
return "libx265"
|
||||||
|
elif self.available_encoders.get("libx264"):
|
||||||
|
return "libx264"
|
||||||
|
|
||||||
|
return "libx264" # Default fallback
|
||||||
|
|
||||||
|
def _get_recommended_settings(self):
|
||||||
|
"""Get recommended encoding settings based on hardware."""
|
||||||
|
settings = {
|
||||||
|
"encoder": self.recommended_encoder,
|
||||||
|
"preset": self._get_preset(),
|
||||||
|
"threads": self._get_thread_count(),
|
||||||
|
"gpu_capable": self.gpu_type is not None,
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings
|
||||||
|
|
||||||
|
def _get_preset(self):
|
||||||
|
"""Get recommended preset based on encoder."""
|
||||||
|
encoder = self.recommended_encoder
|
||||||
|
|
||||||
|
# GPU encoders don't use "preset" in the same way
|
||||||
|
if "nvenc" in encoder or "amf" in encoder or "qsv" in encoder:
|
||||||
|
# GPU presets (0-fast, 1-medium, 2-slow)
|
||||||
|
return 1 # medium (balanced)
|
||||||
|
else:
|
||||||
|
# CPU presets (ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow)
|
||||||
|
if self.cpu_cores <= 4:
|
||||||
|
return "faster"
|
||||||
|
elif self.cpu_cores <= 8:
|
||||||
|
return "fast"
|
||||||
|
else:
|
||||||
|
return "medium"
|
||||||
|
|
||||||
|
def _get_thread_count(self):
|
||||||
|
"""Get recommended thread count for encoding."""
|
||||||
|
# For CPU encoding, use most cores but leave some for system
|
||||||
|
if "nvenc" in self.recommended_encoder or "amf" in self.recommended_encoder or "qsv" in self.recommended_encoder:
|
||||||
|
return 0 # GPU handles it
|
||||||
|
else:
|
||||||
|
# Leave 1-2 cores for system
|
||||||
|
return max(self.cpu_cores - 2, 1)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""Return all hardware info as dictionary."""
|
||||||
|
return {
|
||||||
|
"platform": self.platform_name,
|
||||||
|
"cpu_cores": self.cpu_cores,
|
||||||
|
"gpu_type": self.gpu_type,
|
||||||
|
"recommended_encoder": self.recommended_encoder,
|
||||||
|
"available_encoders": {k: v for k, v in self.available_encoders.items() if v},
|
||||||
|
"recommended_settings": self.recommended_settings,
|
||||||
|
}
|
||||||
|
|
||||||
|
def print_summary(self):
|
||||||
|
"""Print hardware detection summary."""
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("HARDWARE DETECTION SUMMARY")
|
||||||
|
print("="*60)
|
||||||
|
print(f"Platform: {self.platform_name}")
|
||||||
|
print(f"CPU Cores: {self.cpu_cores}")
|
||||||
|
print(f"GPU Type: {self.gpu_type or 'None (CPU only)'}")
|
||||||
|
print(f"\nAvailable Encoders:")
|
||||||
|
for encoder, available in self.available_encoders.items():
|
||||||
|
status = "✓" if available else "✗"
|
||||||
|
print(f" {status} {encoder}")
|
||||||
|
print(f"\nRecommended:")
|
||||||
|
print(f" Encoder: {self.recommended_encoder}")
|
||||||
|
print(f" Preset: {self.recommended_settings.get('preset')}")
|
||||||
|
print(f" Threads: {self.recommended_settings.get('threads')}")
|
||||||
|
print("="*60 + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def detect_hardware():
|
||||||
|
"""Quick detection - returns HardwareInfo object."""
|
||||||
|
return HardwareInfo()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Test the hardware detection
|
||||||
|
hw = detect_hardware()
|
||||||
|
hw.print_summary()
|
||||||
|
|
||||||
|
# Also save as JSON for reference
|
||||||
|
hw_json = hw.to_dict()
|
||||||
|
print("Full Hardware Info (JSON):")
|
||||||
|
print(json.dumps(hw_json, indent=2))
|
||||||
@ -1,35 +1,96 @@
|
|||||||
# core/logger_helper.py
|
|
||||||
import logging
|
import logging
|
||||||
|
import json
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class JsonFormatter(logging.Formatter):
|
||||||
|
"""
|
||||||
|
Custom JSON log formatter for structured logging.
|
||||||
|
"""
|
||||||
|
def format(self, record: logging.LogRecord) -> str:
|
||||||
|
log_object = {
|
||||||
|
"timestamp": datetime.utcfromtimestamp(record.created).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||||
|
"level": record.levelname,
|
||||||
|
"message": record.getMessage(),
|
||||||
|
"module": record.module,
|
||||||
|
"funcName": record.funcName,
|
||||||
|
"line": record.lineno,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Include any extra fields added via logger.info("msg", extra={...})
|
||||||
|
if hasattr(record, "extra") and isinstance(record.extra, dict):
|
||||||
|
log_object.update(record.extra)
|
||||||
|
|
||||||
|
# Include exception info if present
|
||||||
|
if record.exc_info:
|
||||||
|
log_object["exception"] = self.formatException(record.exc_info)
|
||||||
|
|
||||||
|
return json.dumps(log_object, ensure_ascii=False)
|
||||||
|
|
||||||
def setup_logger(log_folder: Path, log_file_name: str = "conversion.log", level=logging.INFO) -> logging.Logger:
|
def setup_logger(log_folder: Path, log_file_name: str = "conversion.log", level=logging.INFO) -> logging.Logger:
|
||||||
"""
|
"""
|
||||||
Sets up a logger that prints to console and writes to a rotating log file.
|
Sets up a logger that prints to console and writes to a rotating JSON log file.
|
||||||
"""
|
"""
|
||||||
log_folder.mkdir(parents=True, exist_ok=True)
|
log_folder.mkdir(parents=True, exist_ok=True)
|
||||||
log_file = log_folder / log_file_name
|
log_file = log_folder / log_file_name
|
||||||
|
|
||||||
logger = logging.getLogger("conversion_logger")
|
logger = logging.getLogger("conversion_logger")
|
||||||
logger.setLevel(level)
|
logger.setLevel(level)
|
||||||
logger.propagate = False # Prevent duplicate logging if root logger exists
|
logger.propagate = False # Prevent double logging
|
||||||
|
|
||||||
# Formatter with timestamp
|
# Formatters
|
||||||
formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
text_formatter = logging.Formatter(
|
||||||
|
"%(asctime)s [%(levelname)s] %(message)s (%(module)s:%(lineno)d)",
|
||||||
|
datefmt="%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
json_formatter = JsonFormatter()
|
||||||
|
|
||||||
# Console handler
|
# Console handler (human-readable)
|
||||||
console_handler = logging.StreamHandler()
|
console_handler = logging.StreamHandler()
|
||||||
console_handler.setFormatter(formatter)
|
console_handler.setFormatter(text_formatter)
|
||||||
console_handler.setLevel(level)
|
console_handler.setLevel(level)
|
||||||
|
|
||||||
# File handler with rotation (max 5 MB per file, keep 3 backups)
|
# File handler (JSON logs)
|
||||||
file_handler = RotatingFileHandler(log_file, maxBytes=5*1024*1024, backupCount=3, encoding="utf-8")
|
file_handler = RotatingFileHandler(log_file, maxBytes=5 * 1024 * 1024, backupCount=3, encoding="utf-8")
|
||||||
file_handler.setFormatter(formatter)
|
file_handler.setFormatter(json_formatter)
|
||||||
file_handler.setLevel(level)
|
file_handler.setLevel(level)
|
||||||
|
|
||||||
# Add handlers
|
# Add handlers only once
|
||||||
if not logger.handlers:
|
if not logger.handlers:
|
||||||
logger.addHandler(console_handler)
|
logger.addHandler(console_handler)
|
||||||
logger.addHandler(file_handler)
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
|
|
||||||
|
def setup_failure_logger(log_folder: Path) -> logging.Logger:
|
||||||
|
"""
|
||||||
|
Setup a dedicated logger for encoding failures.
|
||||||
|
Returns a logger that writes to logs/failure.log
|
||||||
|
"""
|
||||||
|
log_folder.mkdir(parents=True, exist_ok=True)
|
||||||
|
log_file = log_folder / "failure.log"
|
||||||
|
|
||||||
|
logger = logging.getLogger("failure_logger")
|
||||||
|
logger.setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
# Prevent duplicate handlers
|
||||||
|
if logger.handlers:
|
||||||
|
return logger
|
||||||
|
|
||||||
|
# Simple text formatter for failure log
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
"%(asctime)s | %(message)s",
|
||||||
|
datefmt="%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
|
||||||
|
# File handler only
|
||||||
|
file_handler = RotatingFileHandler(log_file, maxBytes=5 * 1024 * 1024, backupCount=3, encoding="utf-8")
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
file_handler.setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
logger.propagate = False
|
||||||
|
|
||||||
|
return logger
|
||||||
|
|||||||
398
core/process_manager.py
Normal file
398
core/process_manager.py
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
# core/process_manager.py
|
||||||
|
"""Main processing logic for batch transcoding."""
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
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
|
||||||
|
|
||||||
|
logger = setup_logger(Path(__file__).parent.parent / "logs")
|
||||||
|
failure_logger = setup_failure_logger(Path(__file__).parent.parent / "logs")
|
||||||
|
|
||||||
|
|
||||||
|
def _cleanup_temp_files(temp_input: Path, temp_output: Path):
|
||||||
|
"""Helper function to clean up temporary input and output files."""
|
||||||
|
try:
|
||||||
|
if temp_input.exists():
|
||||||
|
temp_input.unlink()
|
||||||
|
logger.debug(f"Cleaned up temp input: {temp_input.name}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not delete temp input {temp_input.name}: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if temp_output.exists():
|
||||||
|
temp_output.unlink()
|
||||||
|
logger.debug(f"Cleaned up temp output: {temp_output.name}")
|
||||||
|
except Exception as e:
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
Process all video files in folder with appropriate encoding settings.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
folder: Input folder path
|
||||||
|
cq: CQ override value
|
||||||
|
transcode_mode: "cq" or "bitrate"
|
||||||
|
resolution: Explicit resolution override ("480", "720", "1080", or None for smart)
|
||||||
|
config: Configuration dictionary
|
||||||
|
tracker_file: Path to CSV tracker file
|
||||||
|
"""
|
||||||
|
if not folder.exists():
|
||||||
|
print(f"❌ Folder not found: {folder}")
|
||||||
|
logger.error(f"Folder not found: {folder}")
|
||||||
|
return
|
||||||
|
|
||||||
|
audio_config = config["audio"]
|
||||||
|
bitrate_config = config["encode"]["fallback"]
|
||||||
|
filters_config = config["encode"]["filters"]
|
||||||
|
suffix = config["suffix"]
|
||||||
|
extensions = config["extensions"]
|
||||||
|
ignore_tags = config["ignore_tags"]
|
||||||
|
reduction_ratio_threshold = config["reduction_ratio_threshold"]
|
||||||
|
|
||||||
|
# Resolution logic: explicit arg takes precedence, else use smart defaults
|
||||||
|
explicit_resolution = resolution # Will be None if not specified
|
||||||
|
|
||||||
|
filter_flags = filters_config.get("default","lanczos")
|
||||||
|
folder_lower = str(folder).lower()
|
||||||
|
is_tv = "\\tv\\" in folder_lower or "/tv/" in folder_lower
|
||||||
|
if is_tv:
|
||||||
|
filter_flags = filters_config.get("tv","bicubic")
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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)")
|
||||||
|
elif is_forced_cq:
|
||||||
|
print("📋 MODE: Forced CQ (skip failures, log them)")
|
||||||
|
else:
|
||||||
|
print("📋 MODE: Forced Bitrate (skip failures, log them)")
|
||||||
|
print(f"{'='*60}\n")
|
||||||
|
|
||||||
|
for file in folder.rglob("*"):
|
||||||
|
if file.suffix.lower() not in extensions:
|
||||||
|
continue
|
||||||
|
if any(tag.lower() in file.name.lower() for tag in ignore_tags):
|
||||||
|
print(f"⏭️ Skipping: {file.name}")
|
||||||
|
logger.info(f"Skipping: {file.name}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print("="*60)
|
||||||
|
logger.info(f"Processing: {file.name}")
|
||||||
|
print(f"📁 Processing: {file.name}")
|
||||||
|
|
||||||
|
temp_input = processing_folder / file.name
|
||||||
|
shutil.copy2(file, temp_input)
|
||||||
|
logger.info(f"Copied {file.name} → {temp_input.name}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Detect source resolution and determine target resolution
|
||||||
|
src_width, src_height = get_source_resolution(temp_input)
|
||||||
|
res_width, res_height, target_resolution = determine_target_resolution(
|
||||||
|
src_width, src_height, explicit_resolution
|
||||||
|
)
|
||||||
|
|
||||||
|
# Log resolution decision
|
||||||
|
if explicit_resolution:
|
||||||
|
logger.info(f"Using explicitly specified resolution: {res_width}x{res_height}")
|
||||||
|
else:
|
||||||
|
if src_height > 1080:
|
||||||
|
print(f"⚠️ Source {src_width}x{src_height} is above 1080p. Scaling down to 1080p.")
|
||||||
|
logger.info(f"Source {src_width}x{src_height} detected. Scaling to 1080p.")
|
||||||
|
elif src_height <= 720:
|
||||||
|
print(f"ℹ️ Source {src_width}x{src_height} is 720p or lower. Preserving resolution.")
|
||||||
|
logger.info(f"Source {src_width}x{src_height} (<=720p). Preserving source resolution.")
|
||||||
|
else:
|
||||||
|
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)
|
||||||
|
file_cq = cq if cq is not None else content_cq
|
||||||
|
|
||||||
|
temp_output = processing_folder / f"{file.stem}{suffix}{file.suffix}"
|
||||||
|
|
||||||
|
# Determine which method to try first
|
||||||
|
if is_forced_bitrate:
|
||||||
|
method = "Bitrate"
|
||||||
|
elif is_forced_cq:
|
||||||
|
method = "CQ"
|
||||||
|
else: # Smart mode
|
||||||
|
method = "CQ" # Always try CQ first in smart mode
|
||||||
|
|
||||||
|
# Attempt encoding
|
||||||
|
try:
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if encode met size target
|
||||||
|
encode_succeeded = True
|
||||||
|
if method == "CQ" and reduction_ratio >= reduction_ratio_threshold:
|
||||||
|
encode_succeeded = False
|
||||||
|
elif method == "Bitrate" and reduction_ratio >= reduction_ratio_threshold:
|
||||||
|
encode_succeeded = False
|
||||||
|
|
||||||
|
if not encode_succeeded:
|
||||||
|
# Size threshold not met
|
||||||
|
if is_smart_mode and method == "CQ":
|
||||||
|
# In smart mode CQ failure, mark for bitrate retry
|
||||||
|
print(f"⚠️ CQ failed size target ({reduction_ratio:.1%}). Will retry with Bitrate.")
|
||||||
|
failure_logger.warning(f"{file.name} | CQ failed size target ({reduction_ratio:.1%})")
|
||||||
|
failed_cq_files.append({
|
||||||
|
'file': file,
|
||||||
|
'temp_input': temp_input,
|
||||||
|
'temp_output': temp_output,
|
||||||
|
'src_width': src_width,
|
||||||
|
'src_height': src_height,
|
||||||
|
'res_width': res_width,
|
||||||
|
'res_height': res_height,
|
||||||
|
'target_resolution': target_resolution,
|
||||||
|
'file_cq': file_cq,
|
||||||
|
'is_tv': is_tv
|
||||||
|
})
|
||||||
|
consecutive_failures += 1
|
||||||
|
if consecutive_failures >= max_consecutive:
|
||||||
|
print(f"\n⚠️ {max_consecutive} consecutive CQ failures. Moving to Phase 2: Bitrate retry.")
|
||||||
|
logger.warning(f"{max_consecutive} consecutive CQ failures. Moving to Phase 2.")
|
||||||
|
break # Move to Phase 2
|
||||||
|
continue
|
||||||
|
elif is_forced_cq or is_forced_bitrate:
|
||||||
|
# In forced mode, skip the file
|
||||||
|
error_msg = f"Size threshold not met ({reduction_ratio:.1%})"
|
||||||
|
print(f"❌ {method} failed: {error_msg}")
|
||||||
|
failure_logger.warning(f"{file.name} | {method} failed: {error_msg}")
|
||||||
|
consecutive_failures += 1
|
||||||
|
if consecutive_failures >= max_consecutive:
|
||||||
|
print(f"\n❌ {max_consecutive} consecutive failures in forced {method} mode. Stopping.")
|
||||||
|
logger.error(f"{max_consecutive} consecutive failures. Stopping process.")
|
||||||
|
_cleanup_temp_files(temp_input, temp_output)
|
||||||
|
break
|
||||||
|
_cleanup_temp_files(temp_input, temp_output)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Encoding succeeded - reset failure counter
|
||||||
|
consecutive_failures = 0
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
# FFmpeg execution failed
|
||||||
|
error_msg = str(e).split('\n')[0][:100] # First 100 chars of error
|
||||||
|
|
||||||
|
if is_smart_mode and method == "CQ":
|
||||||
|
# In smart mode, log and retry with bitrate
|
||||||
|
print(f"❌ CQ encode error. Will retry with Bitrate.")
|
||||||
|
failure_logger.warning(f"{file.name} | CQ error: {error_msg}")
|
||||||
|
failed_cq_files.append({
|
||||||
|
'file': file,
|
||||||
|
'temp_input': temp_input,
|
||||||
|
'temp_output': temp_output,
|
||||||
|
'src_width': src_width,
|
||||||
|
'src_height': src_height,
|
||||||
|
'res_width': res_width,
|
||||||
|
'res_height': res_height,
|
||||||
|
'target_resolution': target_resolution,
|
||||||
|
'file_cq': file_cq,
|
||||||
|
'is_tv': is_tv
|
||||||
|
})
|
||||||
|
consecutive_failures += 1
|
||||||
|
if consecutive_failures >= max_consecutive:
|
||||||
|
print(f"\n⚠️ {max_consecutive} consecutive CQ failures. Moving to Phase 2: Bitrate retry.")
|
||||||
|
logger.warning(f"{max_consecutive} consecutive CQ failures. Moving to Phase 2.")
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
elif is_forced_cq or is_forced_bitrate:
|
||||||
|
# In forced mode, skip and log
|
||||||
|
print(f"❌ {method} encode failed: {error_msg}")
|
||||||
|
failure_logger.warning(f"{file.name} | {method} error: {error_msg}")
|
||||||
|
consecutive_failures += 1
|
||||||
|
if consecutive_failures >= max_consecutive:
|
||||||
|
print(f"\n❌ {max_consecutive} consecutive failures in forced {method} mode. Stopping.")
|
||||||
|
logger.error(f"{max_consecutive} consecutive failures. Stopping process.")
|
||||||
|
_cleanup_temp_files(temp_input, temp_output)
|
||||||
|
break
|
||||||
|
_cleanup_temp_files(temp_input, temp_output)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If we get here, encoding succeeded - save file and log
|
||||||
|
_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
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Unexpected error
|
||||||
|
error_msg = str(e)[:100]
|
||||||
|
print(f"❌ Unexpected error: {error_msg}")
|
||||||
|
failure_logger.warning(f"{file.name} | Unexpected error: {error_msg}")
|
||||||
|
consecutive_failures += 1
|
||||||
|
logger.error(f"Unexpected error processing {file.name}: {e}")
|
||||||
|
_cleanup_temp_files(temp_input, temp_output)
|
||||||
|
|
||||||
|
if is_forced_cq or is_forced_bitrate:
|
||||||
|
if consecutive_failures >= max_consecutive:
|
||||||
|
print(f"\n❌ {max_consecutive} consecutive failures. Stopping.")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if consecutive_failures >= max_consecutive:
|
||||||
|
print(f"\n⚠️ {max_consecutive} consecutive failures. Moving to Phase 2.")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Phase 2: Retry failed CQ files with Bitrate mode (smart mode only)
|
||||||
|
if is_smart_mode and failed_cq_files:
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print(f"📋 PHASE 2: Retrying {len(failed_cq_files)} failed files with Bitrate mode")
|
||||||
|
print(f"{'='*60}\n")
|
||||||
|
|
||||||
|
consecutive_failures = 0
|
||||||
|
|
||||||
|
for file_data in failed_cq_files:
|
||||||
|
file = file_data['file']
|
||||||
|
temp_input = file_data['temp_input']
|
||||||
|
temp_output = file_data['temp_output']
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f"🔄 Retrying: {file.name} with Bitrate")
|
||||||
|
logger.info(f"Phase 2 Retry: {file.name} with Bitrate mode")
|
||||||
|
|
||||||
|
# Clean up old output if it exists
|
||||||
|
if temp_output.exists():
|
||||||
|
temp_output.unlink()
|
||||||
|
|
||||||
|
# Retry with bitrate
|
||||||
|
orig_size, out_size, reduction_ratio = run_ffmpeg(
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if bitrate also failed
|
||||||
|
if reduction_ratio >= reduction_ratio_threshold:
|
||||||
|
print(f"⚠️ Bitrate also failed size target ({reduction_ratio:.1%}). Skipping.")
|
||||||
|
failure_logger.warning(f"{file.name} | Bitrate retry also failed ({reduction_ratio:.1%})")
|
||||||
|
consecutive_failures += 1
|
||||||
|
_cleanup_temp_files(temp_input, temp_output)
|
||||||
|
if consecutive_failures >= max_consecutive:
|
||||||
|
print(f"\n⚠️ {max_consecutive} consecutive Phase 2 failures. Stopping retries.")
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Bitrate succeeded
|
||||||
|
consecutive_failures = 0
|
||||||
|
_save_successful_encoding(
|
||||||
|
file, temp_input, temp_output,
|
||||||
|
orig_size, out_size, reduction_ratio, "Bitrate",
|
||||||
|
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']
|
||||||
|
)
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
error_msg = str(e).split('\n')[0][:100]
|
||||||
|
print(f"❌ Bitrate retry failed: {error_msg}")
|
||||||
|
failure_logger.warning(f"{file.name} | Bitrate retry error: {error_msg}")
|
||||||
|
consecutive_failures += 1
|
||||||
|
logger.error(f"Bitrate retry failed for {file.name}: {e}")
|
||||||
|
_cleanup_temp_files(temp_input, temp_output)
|
||||||
|
|
||||||
|
if consecutive_failures >= max_consecutive:
|
||||||
|
print(f"\n⚠️ {max_consecutive} consecutive Phase 2 failures. Stopping retries.")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)[:100]
|
||||||
|
print(f"❌ Unexpected error in Phase 2: {error_msg}")
|
||||||
|
failure_logger.warning(f"{file.name} | Phase 2 error: {error_msg}")
|
||||||
|
consecutive_failures += 1
|
||||||
|
_cleanup_temp_files(temp_input, temp_output)
|
||||||
|
if consecutive_failures >= max_consecutive:
|
||||||
|
print(f"\n⚠️ {max_consecutive} consecutive Phase 2 failures. Stopping retries.")
|
||||||
|
break
|
||||||
|
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print("✅ Batch processing complete")
|
||||||
|
logger.info("Batch processing complete")
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""Helper function to save successfully encoded files."""
|
||||||
|
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]
|
||||||
|
if "tv" in folder_parts:
|
||||||
|
f_type = "tv"
|
||||||
|
tv_index = folder_parts.index("tv")
|
||||||
|
show = folder.parts[tv_index + 1] if len(folder.parts) > tv_index + 1 else "Unknown"
|
||||||
|
elif "anime" in folder_parts:
|
||||||
|
f_type = "anime"
|
||||||
|
anime_index = folder_parts.index("anime")
|
||||||
|
show = folder.parts[anime_index + 1] if len(folder.parts) > anime_index + 1 else "Unknown"
|
||||||
|
else:
|
||||||
|
f_type = "movie"
|
||||||
|
show = "N/A"
|
||||||
|
|
||||||
|
orig_size_mb = round(orig_size / 1e6, 2)
|
||||||
|
proc_size_mb = round(out_size / 1e6, 2)
|
||||||
|
percentage = round(proc_size_mb / orig_size_mb * 100, 1)
|
||||||
|
|
||||||
|
# Get audio stream count for tracking
|
||||||
|
try:
|
||||||
|
audio_streams = get_audio_streams(temp_input)
|
||||||
|
audio_stream_count = len(audio_streams)
|
||||||
|
except:
|
||||||
|
audio_stream_count = 0
|
||||||
|
|
||||||
|
# Format resolutions for tracking
|
||||||
|
src_resolution = f"{src_width}x{src_height}"
|
||||||
|
target_res = f"{res_width}x{res_height}"
|
||||||
|
cq_str = str(file_cq) if method == "CQ" else "N/A"
|
||||||
|
|
||||||
|
with open(tracker_file, "a", newline="", encoding="utf-8") as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow([
|
||||||
|
f_type, show, dest_file.name, orig_size_mb, proc_size_mb, percentage,
|
||||||
|
src_resolution, target_res, audio_stream_count, cq_str, method
|
||||||
|
])
|
||||||
|
|
||||||
|
# Enhanced logging with all conversion details
|
||||||
|
logger.info(f"\n✅ CONVERSION COMPLETE: {dest_file.name}")
|
||||||
|
logger.info(f" Type: {f_type.upper()} | Show: {show}")
|
||||||
|
logger.info(f" Size: {orig_size_mb}MB → {proc_size_mb}MB ({percentage}% of original, {100-percentage:.1f}% reduction)")
|
||||||
|
logger.info(f" Method: {method} | Status: SUCCESS")
|
||||||
|
print(f"📝 Logged conversion: {dest_file.name} ({percentage}%), method={method}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
temp_input.unlink()
|
||||||
|
file.unlink()
|
||||||
|
logger.info(f"Deleted original and processing copy for {file.name}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Could not delete files: {e}")
|
||||||
|
logger.warning(f"Could not delete files: {e}")
|
||||||
69
core/video_handler.py
Normal file
69
core/video_handler.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# core/video_handler.py
|
||||||
|
"""Video resolution detection and encoding logic."""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from core.logger_helper import setup_logger
|
||||||
|
|
||||||
|
logger = setup_logger(Path(__file__).parent.parent / "logs")
|
||||||
|
|
||||||
|
|
||||||
|
def get_source_resolution(input_file: Path) -> tuple:
|
||||||
|
"""
|
||||||
|
Get source video resolution (width, height).
|
||||||
|
Returns tuple: (width, height)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cmd = [
|
||||||
|
"ffprobe", "-v", "error",
|
||||||
|
"-select_streams", "v:0",
|
||||||
|
"-show_entries", "stream=width,height",
|
||||||
|
"-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)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to detect source resolution: {e}. Defaulting to 1920x1080")
|
||||||
|
return (1920, 1080)
|
||||||
|
|
||||||
|
|
||||||
|
def determine_target_resolution(src_width: int, src_height: int, explicit_resolution: str = None) -> tuple:
|
||||||
|
"""
|
||||||
|
Determine target resolution based on source and explicit override.
|
||||||
|
|
||||||
|
Returns tuple: (res_width, res_height, target_resolution_label)
|
||||||
|
|
||||||
|
Logic:
|
||||||
|
If explicit_resolution specified: use it
|
||||||
|
Else:
|
||||||
|
- If source > 1080p: scale to 1080p
|
||||||
|
- If source <= 1080p: preserve source resolution
|
||||||
|
"""
|
||||||
|
if explicit_resolution:
|
||||||
|
# User explicitly specified resolution - always use it
|
||||||
|
if explicit_resolution == "1080":
|
||||||
|
return (1920, 1080, "1080")
|
||||||
|
elif explicit_resolution == "720":
|
||||||
|
return (1280, 720, "720")
|
||||||
|
else: # 480
|
||||||
|
return (854, 480, "480")
|
||||||
|
else:
|
||||||
|
# No explicit resolution - use smart defaults
|
||||||
|
if src_height > 1080:
|
||||||
|
# Scale down anything above 1080p to 1080p
|
||||||
|
logger.info(f"Source {src_width}x{src_height} detected. Scaling to 1080p.")
|
||||||
|
return (1920, 1080, "1080")
|
||||||
|
else:
|
||||||
|
# Preserve source resolution (480p, 720p, 1080p, etc.)
|
||||||
|
if src_height <= 720:
|
||||||
|
logger.info(f"Source {src_width}x{src_height} (<=720p). Preserving source resolution.")
|
||||||
|
return (src_width, src_height, "720")
|
||||||
|
else:
|
||||||
|
logger.info(f"Source {src_width}x{src_height} (<=1080p). Preserving source resolution.")
|
||||||
|
return (src_width, src_height, "1080")
|
||||||
832
gui_path_manager.py
Normal file
832
gui_path_manager.py
Normal file
@ -0,0 +1,832 @@
|
|||||||
|
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
GUI Path Manager for Batch Video Transcoder
|
||||||
|
Allows easy browsing of folders and appending to paths.txt with encoding options.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, messagebox, filedialog
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
from core.config_helper import load_config_xml
|
||||||
|
from core.logger_helper import setup_logger
|
||||||
|
|
||||||
|
logger = setup_logger(Path(__file__).parent / "logs")
|
||||||
|
|
||||||
|
class PathManagerGUI:
|
||||||
|
def __init__(self, root):
|
||||||
|
self.root = root
|
||||||
|
self.root.title("Batch Transcoder - Path Manager")
|
||||||
|
self.root.geometry("1100x700")
|
||||||
|
|
||||||
|
# Load config
|
||||||
|
config_path = Path(__file__).parent / "config.xml"
|
||||||
|
self.config = load_config_xml(config_path)
|
||||||
|
self.path_mappings = self.config.get("path_mappings", {})
|
||||||
|
|
||||||
|
# Paths file
|
||||||
|
self.paths_file = Path(__file__).parent / "paths.txt"
|
||||||
|
self.transcode_bat = Path(__file__).parent / "transcode.bat"
|
||||||
|
|
||||||
|
# Current selected folder
|
||||||
|
self.selected_folder = None
|
||||||
|
self.current_category = None
|
||||||
|
self.recently_added = None # Track recently added folder for highlighting
|
||||||
|
self.status_timer = None # Track status message timer
|
||||||
|
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.mkdir(exist_ok=True)
|
||||||
|
self.folder_cache = {} # Only current category in memory: {folder_path: size}
|
||||||
|
self.scan_in_progress = False
|
||||||
|
self.scanned_categories = set() # Track which categories have been scanned
|
||||||
|
|
||||||
|
# Lazy loading
|
||||||
|
self.all_folders = [] # All folders for current category
|
||||||
|
self.loaded_items = 0 # How many items are currently loaded
|
||||||
|
self.items_per_batch = 100 # Load 100 items at a time
|
||||||
|
|
||||||
|
# Load existing paths
|
||||||
|
self._load_existing_paths()
|
||||||
|
|
||||||
|
# Build UI
|
||||||
|
self._build_ui()
|
||||||
|
|
||||||
|
# Handle window close
|
||||||
|
self.root.protocol("WM_DELETE_WINDOW", self._on_closing)
|
||||||
|
|
||||||
|
def _build_ui(self):
|
||||||
|
"""Build the GUI layout."""
|
||||||
|
# Top frame for category selection and transcode launcher
|
||||||
|
top_frame = ttk.Frame(self.root)
|
||||||
|
top_frame.pack(fill=tk.X, padx=10, pady=10)
|
||||||
|
|
||||||
|
left_top = ttk.Frame(top_frame)
|
||||||
|
left_top.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||||
|
|
||||||
|
ttk.Label(left_top, text="Category:").pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
|
self.category_var = tk.StringVar(value="tv")
|
||||||
|
categories = ["tv", "anime", "movies"]
|
||||||
|
for cat in categories:
|
||||||
|
ttk.Radiobutton(
|
||||||
|
left_top, text=cat.upper(), variable=self.category_var,
|
||||||
|
value=cat, command=self._on_category_change
|
||||||
|
).pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
|
ttk.Button(left_top, text="Refresh", command=self._refresh_with_cache_clear).pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
|
# Right side of top frame - transcode launcher
|
||||||
|
right_top = ttk.Frame(top_frame)
|
||||||
|
right_top.pack(side=tk.RIGHT)
|
||||||
|
|
||||||
|
if self.transcode_bat.exists():
|
||||||
|
ttk.Button(
|
||||||
|
right_top, text="▶ Run transcode.bat", command=self._run_transcode
|
||||||
|
).pack(side=tk.RIGHT, padx=5)
|
||||||
|
|
||||||
|
# Main content frame
|
||||||
|
main_frame = ttk.Frame(self.root)
|
||||||
|
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||||
|
|
||||||
|
# Left side - folder tree
|
||||||
|
left_frame = ttk.LabelFrame(main_frame, text="Folders (sorted by size)")
|
||||||
|
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5))
|
||||||
|
|
||||||
|
# Treeview for folders with add button column
|
||||||
|
self.tree = ttk.Treeview(left_frame, columns=("size", "add", "remove"), height=20)
|
||||||
|
self.tree.column("#0", width=180)
|
||||||
|
self.tree.column("size", width=80)
|
||||||
|
self.tree.column("add", width=50)
|
||||||
|
self.tree.column("remove", width=50)
|
||||||
|
self.tree.heading("#0", text="Folder Name")
|
||||||
|
self.tree.heading("size", text="Size")
|
||||||
|
self.tree.heading("add", text="Add")
|
||||||
|
self.tree.heading("remove", text="Remove")
|
||||||
|
|
||||||
|
scrollbar = ttk.Scrollbar(left_frame, orient=tk.VERTICAL, command=self._on_scrollbar)
|
||||||
|
self.tree.configure(yscroll=scrollbar.set)
|
||||||
|
|
||||||
|
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||||
|
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||||
|
|
||||||
|
# Configure tags for folder status
|
||||||
|
self.tree.tag_configure("added", background="#90EE90") # Light green for added
|
||||||
|
self.tree.tag_configure("not_added", background="white") # White for not added
|
||||||
|
self.tree.tag_configure("recently_added", background="#FFD700") # Gold for recently added
|
||||||
|
|
||||||
|
self.tree.bind("<Double-1>", self._on_folder_expand)
|
||||||
|
self.tree.bind("<<TreeviewSelect>>", self._on_folder_select)
|
||||||
|
self.tree.bind("<Button-1>", self._on_tree_click)
|
||||||
|
|
||||||
|
# Right side - options and preview
|
||||||
|
right_frame = ttk.LabelFrame(main_frame, text="Encoding Options & Preview")
|
||||||
|
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0))
|
||||||
|
|
||||||
|
# Mode selection
|
||||||
|
mode_frame = ttk.LabelFrame(right_frame, text="Mode (--m)")
|
||||||
|
mode_frame.pack(fill=tk.X, padx=5, pady=5)
|
||||||
|
|
||||||
|
self.mode_var = tk.StringVar(value="default")
|
||||||
|
for mode in ["default", "cq", "bitrate"]:
|
||||||
|
ttk.Radiobutton(mode_frame, text=mode, variable=self.mode_var, value=mode,
|
||||||
|
command=self._update_preview).pack(anchor=tk.W, padx=5)
|
||||||
|
|
||||||
|
# Resolution selection
|
||||||
|
res_frame = ttk.LabelFrame(right_frame, text="Resolution (--r)")
|
||||||
|
res_frame.pack(fill=tk.X, padx=5, pady=5)
|
||||||
|
|
||||||
|
self.resolution_var = tk.StringVar(value="none")
|
||||||
|
for res in ["none", "480", "720", "1080"]:
|
||||||
|
label = "Auto" if res == "none" else res + "p"
|
||||||
|
ttk.Radiobutton(res_frame, text=label, variable=self.resolution_var, value=res,
|
||||||
|
command=self._update_preview).pack(anchor=tk.W, padx=5)
|
||||||
|
|
||||||
|
# CQ value
|
||||||
|
cq_frame = ttk.LabelFrame(right_frame, text="CQ Value (--cq, optional)")
|
||||||
|
cq_frame.pack(fill=tk.X, padx=5, pady=5)
|
||||||
|
|
||||||
|
self.cq_var = tk.StringVar(value="")
|
||||||
|
cq_entry = ttk.Entry(cq_frame, textvariable=self.cq_var, width=10)
|
||||||
|
cq_entry.pack(anchor=tk.W, padx=5, pady=3)
|
||||||
|
cq_entry.bind("<KeyRelease>", lambda e: self._update_preview())
|
||||||
|
|
||||||
|
# Preview frame
|
||||||
|
preview_frame = ttk.LabelFrame(right_frame, text="Command Preview")
|
||||||
|
preview_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||||
|
|
||||||
|
self.preview_text = tk.Text(preview_frame, height=8, width=40, wrap=tk.WORD, bg="lightgray")
|
||||||
|
self.preview_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||||
|
self.preview_text.config(state=tk.DISABLED)
|
||||||
|
|
||||||
|
# Bottom frame - action buttons and status
|
||||||
|
bottom_frame = ttk.Frame(self.root)
|
||||||
|
bottom_frame.pack(fill=tk.X, padx=10, pady=10)
|
||||||
|
|
||||||
|
button_frame = ttk.Frame(bottom_frame)
|
||||||
|
button_frame.pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
ttk.Button(button_frame, text="View paths.txt", command=self._view_paths_file).pack(side=tk.LEFT, padx=5)
|
||||||
|
ttk.Button(button_frame, text="Clear paths.txt", command=self._clear_paths_file).pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
|
# Status label (for silent feedback)
|
||||||
|
self.status_label = ttk.Label(bottom_frame, text="", foreground="green")
|
||||||
|
self.status_label.pack(side=tk.LEFT, padx=10)
|
||||||
|
|
||||||
|
# Load cache and populate initial category
|
||||||
|
self._load_cache()
|
||||||
|
self._refresh_folders(use_cache=True)
|
||||||
|
# Only scan once per category on first view
|
||||||
|
if self.current_category not in self.scanned_categories:
|
||||||
|
self.root.after(100, self._scan_folders_once)
|
||||||
|
|
||||||
|
def _on_category_change(self):
|
||||||
|
"""Handle category radio button change."""
|
||||||
|
self.current_category = self.category_var.get()
|
||||||
|
# Load cache for this category
|
||||||
|
self._load_cache()
|
||||||
|
# Show cached data first
|
||||||
|
self._refresh_folders(use_cache=True)
|
||||||
|
# Only scan once per category on first view
|
||||||
|
if self.current_category not in self.scanned_categories:
|
||||||
|
self.root.after(100, self._scan_folders_once)
|
||||||
|
|
||||||
|
def _load_cache(self):
|
||||||
|
"""Load folder cache for current category from disk (lazy)."""
|
||||||
|
category = self.category_var.get()
|
||||||
|
cache_file = self.cache_dir / f".cache_{category}.json"
|
||||||
|
|
||||||
|
self.folder_cache.clear()
|
||||||
|
|
||||||
|
# Don't fully load cache yet - just verify it exists
|
||||||
|
if not cache_file.exists():
|
||||||
|
logger.info(f"No cache file for {category}")
|
||||||
|
else:
|
||||||
|
logger.info(f"Cache file exists for {category}")
|
||||||
|
|
||||||
|
def _parse_cache_lazily(self, limit=None):
|
||||||
|
"""Parse cache file lazily and return folders."""
|
||||||
|
category = self.category_var.get()
|
||||||
|
cache_file = self.cache_dir / f".cache_{category}.json"
|
||||||
|
|
||||||
|
folders = []
|
||||||
|
|
||||||
|
if cache_file.exists():
|
||||||
|
try:
|
||||||
|
with open(cache_file, "r", encoding="utf-8") as f:
|
||||||
|
cache_dict = json.load(f)
|
||||||
|
|
||||||
|
# Convert to list and sort
|
||||||
|
for folder_path_str, size in cache_dict.items():
|
||||||
|
folder_path = Path(folder_path_str)
|
||||||
|
if folder_path.exists():
|
||||||
|
folders.append((folder_path.name, folder_path, size))
|
||||||
|
|
||||||
|
# Early exit if limit reached
|
||||||
|
if limit and len(folders) >= limit:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Sort by size descending (only what we loaded)
|
||||||
|
folders.sort(key=lambda x: x[2], reverse=True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to parse cache: {e}")
|
||||||
|
|
||||||
|
return folders
|
||||||
|
|
||||||
|
def _save_cache(self):
|
||||||
|
"""Save current category's folder cache to disk."""
|
||||||
|
category = self.category_var.get()
|
||||||
|
cache_file = self.cache_dir / f".cache_{category}.json"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(cache_file, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(self.folder_cache, f, indent=2)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to save {category} cache: {e}")
|
||||||
|
|
||||||
|
def _refresh_with_cache_clear(self):
|
||||||
|
"""Refresh and clear cache to force full scan."""
|
||||||
|
category = self.category_var.get()
|
||||||
|
cache_file = self.cache_dir / f".cache_{category}.json"
|
||||||
|
|
||||||
|
# Delete cache file for this category
|
||||||
|
if cache_file.exists():
|
||||||
|
cache_file.unlink()
|
||||||
|
|
||||||
|
self.folder_cache.clear()
|
||||||
|
self.scanned_categories.discard(category) # Reset so it will scan again
|
||||||
|
self._refresh_folders(use_cache=False)
|
||||||
|
|
||||||
|
def _scan_folders_once(self):
|
||||||
|
"""Scan folders once per category on first load."""
|
||||||
|
if self.scan_in_progress:
|
||||||
|
return
|
||||||
|
|
||||||
|
category = self.category_var.get()
|
||||||
|
if category in self.scanned_categories:
|
||||||
|
return # Already scanned this category
|
||||||
|
|
||||||
|
self.scan_in_progress = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
category_mapping = {
|
||||||
|
"tv": "P:\\tv",
|
||||||
|
"anime": "P:\\anime",
|
||||||
|
"movies": "P:\\movies"
|
||||||
|
}
|
||||||
|
|
||||||
|
base_key = category_mapping.get(category)
|
||||||
|
if not base_key or base_key not in self.path_mappings:
|
||||||
|
return
|
||||||
|
|
||||||
|
base_path = Path(base_key)
|
||||||
|
if not base_path.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Scan folders and update cache
|
||||||
|
new_cache = {}
|
||||||
|
for entry in os.scandir(base_path):
|
||||||
|
if entry.is_dir(follow_symlinks=False):
|
||||||
|
size = self._get_folder_size(Path(entry))
|
||||||
|
new_cache[str(Path(entry))] = size
|
||||||
|
|
||||||
|
# Update cache and save
|
||||||
|
self.folder_cache = new_cache
|
||||||
|
self._save_cache()
|
||||||
|
self.scanned_categories.add(category)
|
||||||
|
|
||||||
|
# Update UI if still on same category
|
||||||
|
if self.category_var.get() == category:
|
||||||
|
self._refresh_folders(use_cache=True)
|
||||||
|
finally:
|
||||||
|
self.scan_in_progress = False
|
||||||
|
|
||||||
|
def _scan_folders_background(self):
|
||||||
|
"""Scan folders in background and update cache."""
|
||||||
|
if self.scan_in_progress:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.scan_in_progress = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
category = self.category_var.get()
|
||||||
|
category_mapping = {
|
||||||
|
"tv": "P:\\tv",
|
||||||
|
"anime": "P:\\anime",
|
||||||
|
"movies": "P:\\movies"
|
||||||
|
}
|
||||||
|
|
||||||
|
base_key = category_mapping.get(category)
|
||||||
|
if not base_key or base_key not in self.path_mappings:
|
||||||
|
return
|
||||||
|
|
||||||
|
base_path = Path(base_key)
|
||||||
|
if not base_path.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Scan folders and update cache
|
||||||
|
new_cache = {}
|
||||||
|
for entry in os.scandir(base_path):
|
||||||
|
if entry.is_dir(follow_symlinks=False):
|
||||||
|
size = self._get_folder_size(Path(entry))
|
||||||
|
new_cache[str(Path(entry))] = size
|
||||||
|
|
||||||
|
# Update cache and save
|
||||||
|
self.folder_cache[category] = new_cache
|
||||||
|
self._save_cache()
|
||||||
|
|
||||||
|
# Update UI if still on same category
|
||||||
|
if self.category_var.get() == category:
|
||||||
|
self._refresh_folders(use_cache=True)
|
||||||
|
finally:
|
||||||
|
self.scan_in_progress = False
|
||||||
|
# Schedule next continuous scan
|
||||||
|
self.background_scan_timer = self.root.after(
|
||||||
|
self.background_scan_interval,
|
||||||
|
self._continuous_background_scan
|
||||||
|
)
|
||||||
|
# Schedule next continuous scan
|
||||||
|
self.background_scan_timer = self.root.after(
|
||||||
|
self.background_scan_interval,
|
||||||
|
self._continuous_background_scan
|
||||||
|
)
|
||||||
|
|
||||||
|
def _load_existing_paths(self):
|
||||||
|
"""Load existing paths from paths.txt and extract folder paths."""
|
||||||
|
self.added_folders.clear()
|
||||||
|
|
||||||
|
if not self.paths_file.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.paths_file, "r", encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Extract the path (last argument in the command)
|
||||||
|
# Format: --m mode --r res --cq val "path" or just "path"
|
||||||
|
# Find all quoted strings
|
||||||
|
matches = re.findall(r'"([^"]*)"', line)
|
||||||
|
if matches:
|
||||||
|
# Last quoted string is the path
|
||||||
|
path = matches[-1]
|
||||||
|
self.added_folders.add(path)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to load existing paths: {e}")
|
||||||
|
|
||||||
|
def _get_folder_size(self, path: Path) -> int:
|
||||||
|
"""Calculate total size of folder in bytes."""
|
||||||
|
total = 0
|
||||||
|
try:
|
||||||
|
for entry in os.scandir(path):
|
||||||
|
if entry.is_file(follow_symlinks=False):
|
||||||
|
total += entry.stat().st_size
|
||||||
|
elif entry.is_dir(follow_symlinks=False):
|
||||||
|
total += self._get_folder_size(Path(entry))
|
||||||
|
except PermissionError:
|
||||||
|
pass
|
||||||
|
return total
|
||||||
|
|
||||||
|
def _format_size(self, bytes_size: int) -> str:
|
||||||
|
"""Format bytes to human readable size."""
|
||||||
|
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
||||||
|
if bytes_size < 1024:
|
||||||
|
return f"{bytes_size:.1f} {unit}"
|
||||||
|
bytes_size /= 1024
|
||||||
|
return f"{bytes_size:.1f} PB"
|
||||||
|
|
||||||
|
def _refresh_folders(self, use_cache=False):
|
||||||
|
"""Refresh the folder tree from cache or disk."""
|
||||||
|
# Clear existing items
|
||||||
|
for item in self.tree.get_children():
|
||||||
|
self.tree.delete(item)
|
||||||
|
|
||||||
|
self.all_folders = []
|
||||||
|
self.loaded_items = 0
|
||||||
|
|
||||||
|
category = self.category_var.get()
|
||||||
|
|
||||||
|
# Map category to path mapping key
|
||||||
|
category_mapping = {
|
||||||
|
"tv": "P:\\tv",
|
||||||
|
"anime": "P:\\anime",
|
||||||
|
"movies": "P:\\movies"
|
||||||
|
}
|
||||||
|
|
||||||
|
base_key = category_mapping.get(category)
|
||||||
|
if not base_key or base_key not in self.path_mappings:
|
||||||
|
messagebox.showwarning("Info", f"No mapping found for {category}")
|
||||||
|
return
|
||||||
|
|
||||||
|
base_path = Path(base_key)
|
||||||
|
|
||||||
|
# Check if path exists
|
||||||
|
if not base_path.exists():
|
||||||
|
messagebox.showerror("Error", f"Path not found: {base_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get folders from cache or disk
|
||||||
|
if use_cache:
|
||||||
|
# Parse cache lazily - only load what we need initially
|
||||||
|
folders = self._parse_cache_lazily(limit=None) # Get all but parse efficiently
|
||||||
|
else:
|
||||||
|
# Scan from disk
|
||||||
|
folders = []
|
||||||
|
try:
|
||||||
|
for entry in os.scandir(base_path):
|
||||||
|
if entry.is_dir(follow_symlinks=False):
|
||||||
|
size = self._get_folder_size(Path(entry))
|
||||||
|
folders.append((entry.name, Path(entry), size))
|
||||||
|
except PermissionError:
|
||||||
|
messagebox.showerror("Error", f"Permission denied accessing {base_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update cache with fresh scan
|
||||||
|
cache_dict = {str(f[1]): f[2] for f in folders}
|
||||||
|
self.folder_cache = cache_dict
|
||||||
|
self._save_cache()
|
||||||
|
|
||||||
|
# Sort by size descending
|
||||||
|
folders.sort(key=lambda x: x[2], reverse=True)
|
||||||
|
|
||||||
|
# Store all folders and load first batch only
|
||||||
|
self.all_folders = folders
|
||||||
|
self._load_more_items()
|
||||||
|
|
||||||
|
def _on_folder_expand(self, event):
|
||||||
|
"""Handle folder double-click to show contents."""
|
||||||
|
selection = self.tree.selection()
|
||||||
|
if not selection:
|
||||||
|
return
|
||||||
|
|
||||||
|
item = selection[0]
|
||||||
|
tags = self.tree.item(item, "tags")
|
||||||
|
|
||||||
|
if not tags:
|
||||||
|
return
|
||||||
|
|
||||||
|
folder_path = Path(tags[0])
|
||||||
|
|
||||||
|
# Check if already expanded
|
||||||
|
if self.tree.get_children(item):
|
||||||
|
# Toggle: remove children
|
||||||
|
for child in self.tree.get_children(item):
|
||||||
|
self.tree.delete(child)
|
||||||
|
else:
|
||||||
|
# Add file/folder contents
|
||||||
|
try:
|
||||||
|
entries = []
|
||||||
|
for entry in os.scandir(folder_path):
|
||||||
|
if entry.is_file():
|
||||||
|
size = entry.stat().st_size
|
||||||
|
entries.append((entry.name, "File", size))
|
||||||
|
elif entry.is_dir():
|
||||||
|
size = self._get_folder_size(Path(entry))
|
||||||
|
entries.append((entry.name, "Folder", size))
|
||||||
|
|
||||||
|
# Sort by size descending
|
||||||
|
entries.sort(key=lambda x: x[2], reverse=True)
|
||||||
|
|
||||||
|
for name, type_str, size in entries:
|
||||||
|
size_str = self._format_size(size)
|
||||||
|
self.tree.insert(item, "end", text=f"[{type_str}] {name}", values=(size_str,))
|
||||||
|
except PermissionError:
|
||||||
|
messagebox.showerror("Error", f"Permission denied accessing {folder_path}")
|
||||||
|
|
||||||
|
def _on_folder_select(self, event):
|
||||||
|
"""Handle folder selection to update preview."""
|
||||||
|
selection = self.tree.selection()
|
||||||
|
if not selection:
|
||||||
|
return
|
||||||
|
|
||||||
|
item = selection[0]
|
||||||
|
tags = self.tree.item(item, "tags")
|
||||||
|
|
||||||
|
if tags:
|
||||||
|
self.selected_folder = tags[0]
|
||||||
|
self._update_preview()
|
||||||
|
|
||||||
|
def _on_tree_click(self, event):
|
||||||
|
"""Handle click on '+' or '-' button in add column."""
|
||||||
|
item = self.tree.identify("item", event.x, event.y)
|
||||||
|
column = self.tree.identify_column(event.x) # Only takes x coordinate
|
||||||
|
|
||||||
|
# Column #2 is the "add" column (columns are #0=name, #1=size, #2=add, #3=remove)
|
||||||
|
if item and column == "#2":
|
||||||
|
tags = self.tree.item(item, "tags")
|
||||||
|
if tags:
|
||||||
|
folder_path = tags[0]
|
||||||
|
values = self.tree.item(item, "values")
|
||||||
|
if len(values) > 1:
|
||||||
|
button_text = values[1] # Get button text
|
||||||
|
|
||||||
|
if "[+]" in button_text:
|
||||||
|
# Immediately update UI for snappy response
|
||||||
|
size_val = values[0]
|
||||||
|
self.tree.item(item, values=(size_val, "", "[-]"), tags=(folder_path, "added"))
|
||||||
|
|
||||||
|
# Add to paths.txt asynchronously
|
||||||
|
self.selected_folder = folder_path
|
||||||
|
self.root.after(0, self._add_to_paths_file_async, folder_path)
|
||||||
|
|
||||||
|
# Column #3 is the "remove" column
|
||||||
|
elif item and column == "#3":
|
||||||
|
tags = self.tree.item(item, "tags")
|
||||||
|
if tags:
|
||||||
|
folder_path = tags[0]
|
||||||
|
|
||||||
|
# Immediately update UI for snappy response
|
||||||
|
values = self.tree.item(item, "values")
|
||||||
|
size_val = values[0]
|
||||||
|
self.tree.item(item, values=(size_val, "[+]", ""), tags=(folder_path, "not_added"))
|
||||||
|
|
||||||
|
# Remove from paths.txt asynchronously
|
||||||
|
self.root.after(0, self._remove_from_paths_file_async, folder_path)
|
||||||
|
|
||||||
|
def _add_to_paths_file_async(self, folder_path):
|
||||||
|
"""Add to paths.txt without blocking UI."""
|
||||||
|
self.selected_folder = folder_path
|
||||||
|
self._add_to_paths_file()
|
||||||
|
# Silently reload in background
|
||||||
|
self._load_existing_paths()
|
||||||
|
|
||||||
|
def _remove_from_paths_file_async(self, folder_path):
|
||||||
|
"""Remove from paths.txt without blocking UI."""
|
||||||
|
self._remove_from_paths_file(folder_path)
|
||||||
|
|
||||||
|
def _update_preview(self):
|
||||||
|
"""Update the command preview."""
|
||||||
|
if not self.selected_folder:
|
||||||
|
preview_text = "No folder selected"
|
||||||
|
else:
|
||||||
|
folder_path = self.selected_folder
|
||||||
|
|
||||||
|
# Build command
|
||||||
|
cmd_parts = ['py main.py']
|
||||||
|
|
||||||
|
# Add mode if not default
|
||||||
|
mode = self.mode_var.get()
|
||||||
|
if mode != "default":
|
||||||
|
cmd_parts.append(f'--m {mode}')
|
||||||
|
|
||||||
|
# Add resolution if specified
|
||||||
|
resolution = self.resolution_var.get()
|
||||||
|
if resolution != "none":
|
||||||
|
cmd_parts.append(f'--r {resolution}')
|
||||||
|
|
||||||
|
# Add CQ if specified
|
||||||
|
cq = self.cq_var.get().strip()
|
||||||
|
if cq:
|
||||||
|
cmd_parts.append(f'--cq {cq}')
|
||||||
|
|
||||||
|
# Add path
|
||||||
|
cmd_parts.append(f'"{folder_path}"')
|
||||||
|
|
||||||
|
preview_text = " ".join(cmd_parts)
|
||||||
|
|
||||||
|
self.preview_text.config(state=tk.NORMAL)
|
||||||
|
self.preview_text.delete("1.0", tk.END)
|
||||||
|
self.preview_text.insert("1.0", preview_text)
|
||||||
|
self.preview_text.config(state=tk.DISABLED)
|
||||||
|
|
||||||
|
def _add_to_paths_file(self):
|
||||||
|
"""Append the current command to paths.txt."""
|
||||||
|
if not self.selected_folder:
|
||||||
|
messagebox.showwarning("Warning", "Please select a folder first")
|
||||||
|
return
|
||||||
|
|
||||||
|
folder_path = self.selected_folder
|
||||||
|
|
||||||
|
# Check if already in file
|
||||||
|
if folder_path in self.added_folders:
|
||||||
|
self._show_status(f"Already added: {Path(folder_path).name}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Build command line - start fresh
|
||||||
|
cmd_parts = []
|
||||||
|
|
||||||
|
# Add mode if not default
|
||||||
|
mode = self.mode_var.get()
|
||||||
|
if mode != "default":
|
||||||
|
cmd_parts.append(f'--m {mode}')
|
||||||
|
|
||||||
|
# Add resolution if specified
|
||||||
|
resolution = self.resolution_var.get()
|
||||||
|
if resolution != "none":
|
||||||
|
cmd_parts.append(f'--r {resolution}')
|
||||||
|
|
||||||
|
# Add CQ if specified
|
||||||
|
cq = self.cq_var.get().strip()
|
||||||
|
if cq:
|
||||||
|
cmd_parts.append(f'--cq {cq}')
|
||||||
|
|
||||||
|
# Add folder path
|
||||||
|
cmd_parts.append(f'"{folder_path}"')
|
||||||
|
|
||||||
|
line = " ".join(cmd_parts)
|
||||||
|
|
||||||
|
# Append to paths.txt
|
||||||
|
try:
|
||||||
|
# Check if file exists and has content
|
||||||
|
if self.paths_file.exists() and self.paths_file.stat().st_size > 0:
|
||||||
|
# Read last character to check if it ends with newline
|
||||||
|
with open(self.paths_file, "rb") as f:
|
||||||
|
f.seek(-1, 2) # Seek to last byte
|
||||||
|
last_char = f.read(1)
|
||||||
|
needs_newline = last_char != b'\n'
|
||||||
|
else:
|
||||||
|
needs_newline = False
|
||||||
|
|
||||||
|
# Write to file
|
||||||
|
with open(self.paths_file, "a", encoding="utf-8") as f:
|
||||||
|
if needs_newline:
|
||||||
|
f.write("\n")
|
||||||
|
f.write(line + "\n")
|
||||||
|
|
||||||
|
# Add to tracked set
|
||||||
|
self.added_folders.add(folder_path)
|
||||||
|
|
||||||
|
# Silent success - show status label instead of popup
|
||||||
|
self.recently_added = folder_path
|
||||||
|
self._show_status(f"✓ Added: {Path(folder_path).name}")
|
||||||
|
logger.info(f"Added to paths.txt: {line}")
|
||||||
|
|
||||||
|
# Clear timer if exists
|
||||||
|
if self.status_timer:
|
||||||
|
self.root.after_cancel(self.status_timer)
|
||||||
|
|
||||||
|
# Clear status after 3 seconds
|
||||||
|
self.status_timer = self.root.after(3000, self._clear_status)
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Failed to write to paths.txt: {e}")
|
||||||
|
logger.error(f"Failed to write to paths.txt: {e}")
|
||||||
|
|
||||||
|
def _remove_from_paths_file(self, folder_path):
|
||||||
|
"""Remove a folder from paths.txt."""
|
||||||
|
if not self.paths_file.exists():
|
||||||
|
messagebox.showwarning("Warning", "paths.txt does not exist")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.paths_file, "r", encoding="utf-8") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
# Filter out lines containing this folder path
|
||||||
|
new_lines = []
|
||||||
|
found = False
|
||||||
|
for line in lines:
|
||||||
|
if f'"{folder_path}"' in line or f"'{folder_path}'" in line:
|
||||||
|
found = True
|
||||||
|
else:
|
||||||
|
new_lines.append(line)
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
messagebox.showwarning("Warning", "Path not found in paths.txt")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Write back
|
||||||
|
with open(self.paths_file, "w", encoding="utf-8") as f:
|
||||||
|
f.writelines(new_lines)
|
||||||
|
|
||||||
|
# Remove from tracked set
|
||||||
|
self.added_folders.discard(folder_path)
|
||||||
|
|
||||||
|
self._show_status(f"✓ Removed: {Path(folder_path).name}")
|
||||||
|
logger.info(f"Removed from paths.txt: {folder_path}")
|
||||||
|
|
||||||
|
# Clear timer if exists
|
||||||
|
if self.status_timer:
|
||||||
|
self.root.after_cancel(self.status_timer)
|
||||||
|
|
||||||
|
# Clear status after 3 seconds
|
||||||
|
self.status_timer = self.root.after(3000, self._clear_status)
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Failed to remove from paths.txt: {e}")
|
||||||
|
logger.error(f"Failed to remove from paths.txt: {e}")
|
||||||
|
|
||||||
|
def _show_status(self, message):
|
||||||
|
"""Show status message in label."""
|
||||||
|
self.status_label.config(text=message, foreground="green")
|
||||||
|
|
||||||
|
def _clear_status(self):
|
||||||
|
"""Clear status message after delay."""
|
||||||
|
self.status_label.config(text="")
|
||||||
|
self.status_timer = None
|
||||||
|
|
||||||
|
def _run_transcode(self):
|
||||||
|
"""Launch transcode.bat in a new command window."""
|
||||||
|
if not self.transcode_bat.exists():
|
||||||
|
messagebox.showerror("Error", f"transcode.bat not found at {self.transcode_bat}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Launch in new cmd window
|
||||||
|
subprocess.Popen(
|
||||||
|
['cmd', '/c', f'"{self.transcode_bat}"'],
|
||||||
|
cwd=str(self.transcode_bat.parent),
|
||||||
|
creationflags=subprocess.CREATE_NEW_CONSOLE
|
||||||
|
)
|
||||||
|
logger.info("Launched transcode.bat")
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Failed to launch transcode.bat: {e}")
|
||||||
|
logger.error(f"Failed to launch transcode.bat: {e}")
|
||||||
|
|
||||||
|
def _view_paths_file(self):
|
||||||
|
"""Open paths.txt in a new window."""
|
||||||
|
if not self.paths_file.exists():
|
||||||
|
messagebox.showinfo("Info", "paths.txt does not exist yet")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.paths_file, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Create new window
|
||||||
|
view_window = tk.Toplevel(self.root)
|
||||||
|
view_window.title("paths.txt")
|
||||||
|
view_window.geometry("800x400")
|
||||||
|
|
||||||
|
text_widget = tk.Text(view_window, wrap=tk.WORD)
|
||||||
|
text_widget.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||||
|
text_widget.insert("1.0", content)
|
||||||
|
|
||||||
|
# Add close button
|
||||||
|
ttk.Button(view_window, text="Close", command=view_window.destroy).pack(pady=5)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Failed to read paths.txt: {e}")
|
||||||
|
|
||||||
|
def _clear_paths_file(self):
|
||||||
|
"""Clear the paths.txt file."""
|
||||||
|
if not self.paths_file.exists():
|
||||||
|
messagebox.showinfo("Info", "paths.txt does not exist")
|
||||||
|
return
|
||||||
|
|
||||||
|
if messagebox.askyesno("Confirm", "Are you sure you want to clear paths.txt?"):
|
||||||
|
try:
|
||||||
|
self.paths_file.write_text("", encoding="utf-8")
|
||||||
|
messagebox.showinfo("Success", "paths.txt has been cleared")
|
||||||
|
logger.info("paths.txt cleared")
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Failed to clear paths.txt: {e}")
|
||||||
|
|
||||||
|
def _on_closing(self):
|
||||||
|
"""Handle window closing - cleanup timers."""
|
||||||
|
self.root.destroy()
|
||||||
|
|
||||||
|
def _on_scrollbar(self, *args):
|
||||||
|
"""Handle scrollbar movement - load more items when scrolling."""
|
||||||
|
self.tree.yview(*args)
|
||||||
|
|
||||||
|
# Check if we need to load more items
|
||||||
|
if self.all_folders and self.loaded_items < len(self.all_folders):
|
||||||
|
# Get scroll position
|
||||||
|
first_visible = self.tree.yview()[0]
|
||||||
|
last_visible = self.tree.yview()[1]
|
||||||
|
|
||||||
|
# If we're past 70% scrolled, load more
|
||||||
|
if last_visible > 0.7:
|
||||||
|
self._load_more_items()
|
||||||
|
|
||||||
|
def _load_more_items(self):
|
||||||
|
"""Load next batch of items into tree."""
|
||||||
|
start = self.loaded_items
|
||||||
|
end = min(start + self.items_per_batch, len(self.all_folders))
|
||||||
|
|
||||||
|
for i in range(start, end):
|
||||||
|
folder_name, folder_path, size = self.all_folders[i]
|
||||||
|
size_str = self._format_size(size)
|
||||||
|
folder_path_str = str(folder_path)
|
||||||
|
|
||||||
|
# Determine button and tag
|
||||||
|
if folder_path_str in self.added_folders:
|
||||||
|
add_btn = ""
|
||||||
|
remove_btn = "[-]"
|
||||||
|
tag = "added"
|
||||||
|
else:
|
||||||
|
add_btn = "[+]"
|
||||||
|
remove_btn = ""
|
||||||
|
tag = "not_added"
|
||||||
|
|
||||||
|
self.tree.insert("", "end", text=folder_name, values=(size_str, add_btn, remove_btn),
|
||||||
|
tags=(folder_path_str, tag))
|
||||||
|
|
||||||
|
self.loaded_items = end
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
root = tk.Tk()
|
||||||
|
app = PathManagerGUI(root)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -4191,3 +4191,902 @@
|
|||||||
2025-12-31 11:44:03 [INFO] - Stream #2: 2ch→2ch, src=und, detected=65kbps, action=COPY (preserve), target=65kbps
|
2025-12-31 11:44:03 [INFO] - Stream #2: 2ch→2ch, src=und, detected=65kbps, action=COPY (preserve), target=65kbps
|
||||||
2025-12-31 11:44:03 [INFO] Running CQ encode: How the Grinch Stole Christmas (2000) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv
|
2025-12-31 11:44:03 [INFO] Running CQ encode: How the Grinch Stole Christmas (2000) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv
|
||||||
2025-12-31 11:52:44 [INFO] 📦 Original: 5626.25 MB → Encoded: 3268.49 MB (58.1% of original)
|
2025-12-31 11:52:44 [INFO] 📦 Original: 5626.25 MB → Encoded: 3268.49 MB (58.1% of original)
|
||||||
|
2025-12-31 11:53:12 [INFO] Moved How the Grinch Stole Christmas (2000) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv → How the Grinch Stole Christmas (2000) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv
|
||||||
|
2025-12-31 11:53:12 [INFO] Tracked conversion: How the Grinch Stole Christmas (2000) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv, 5626.25MB → 3268.49MB (58.1%), method=CQ
|
||||||
|
2025-12-31 11:53:13 [INFO] Deleted original and processing copy for How the Grinch Stole Christmas (2000) x265 AAC 5.1 Bluray-1080p Tigole.mkv
|
||||||
|
2025-12-31 12:06:59 [INFO] Processing: Oppenheimer (2023) x265 AAC 5.1 Bluray-1080p Tigole.mkv
|
||||||
|
2025-12-31 12:08:20 [INFO] Copied Oppenheimer (2023) x265 AAC 5.1 Bluray-1080p Tigole.mkv → Oppenheimer (2023) x265 AAC 5.1 Bluray-1080p Tigole.mkv
|
||||||
|
2025-12-31 12:08:20 [INFO] Source resolution detected: 1920x1080
|
||||||
|
2025-12-31 12:08:20 [INFO] Source 1920x1080 (<=1080p). Preserving source resolution.
|
||||||
|
2025-12-31 12:08:20 [INFO] Source 1920x1080 (<=1080p). Preserving source resolution.
|
||||||
|
2025-12-31 12:08:26 [INFO]
|
||||||
|
🧩 ENCODE SETTINGS
|
||||||
|
2025-12-31 12:08:26 [INFO] Video:
|
||||||
|
2025-12-31 12:08:26 [INFO] • Source Resolution: 1920x1080
|
||||||
|
2025-12-31 12:08:26 [INFO] • Target Resolution: 1920x1080
|
||||||
|
2025-12-31 12:08:26 [INFO] • Encoder: av1_nvenc (preset p1, pix_fmt p010le)
|
||||||
|
2025-12-31 12:08:26 [INFO] • Scale Filter: lanczos
|
||||||
|
2025-12-31 12:08:26 [INFO] • Encode Method: CQ
|
||||||
|
2025-12-31 12:08:26 [INFO] • CQ Value: 32
|
||||||
|
2025-12-31 12:08:26 [INFO] Audio Streams (1 detected):
|
||||||
|
2025-12-31 12:08:26 [INFO] - Stream #1: 6ch→6ch | Lang: und | Detected: 418kbps | Action: ENCODE | Target: 384kbps
|
||||||
|
2025-12-31 12:08:26 [INFO] Running CQ encode: Oppenheimer (2023) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv
|
||||||
|
2025-12-31 12:21:02 [INFO]
|
||||||
|
📊 ENCODE RESULTS:
|
||||||
|
2025-12-31 12:21:02 [INFO] Original Size: 9344.87 MB
|
||||||
|
2025-12-31 12:21:02 [INFO] Encoded Size: 3040.30 MB
|
||||||
|
2025-12-31 12:21:02 [INFO] Reduction: 32.5% of original (67.5% saved)
|
||||||
|
2025-12-31 12:21:02 [INFO] Resolution: 1920x1080 → 1920x1080
|
||||||
|
2025-12-31 12:21:02 [INFO] Audio Streams: 1 streams processed
|
||||||
|
2025-12-31 12:21:28 [INFO] Moved Oppenheimer (2023) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv → Oppenheimer (2023) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv
|
||||||
|
2025-12-31 12:21:39 [INFO]
|
||||||
|
✅ CONVERSION COMPLETE: Oppenheimer (2023) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv
|
||||||
|
2025-12-31 12:21:39 [INFO] Type: MOVIE | Show: N/A
|
||||||
|
2025-12-31 12:21:39 [INFO] Size: 9344.87MB → 3040.3MB (32.5% of original, 67.5% reduction)
|
||||||
|
2025-12-31 12:21:39 [INFO] Method: CQ | Status: SUCCESS
|
||||||
|
2025-12-31 12:21:40 [INFO] Deleted original and processing copy for Oppenheimer (2023) x265 AAC 5.1 Bluray-1080p Tigole.mkv
|
||||||
|
2025-12-31 12:21:40 [INFO] Processing: Innovations in Film - 65mm Black-and-White Film in Oppenheimer.mkv
|
||||||
|
2025-12-31 12:21:41 [INFO] Copied Innovations in Film - 65mm Black-and-White Film in Oppenheimer.mkv → Innovations in Film - 65mm Black-and-White Film in Oppenheimer.mkv
|
||||||
|
2025-12-31 12:21:41 [INFO] Source resolution detected: 1920x1080
|
||||||
|
2025-12-31 12:21:41 [INFO] Source 1920x1080 (<=1080p). Preserving source resolution.
|
||||||
|
2025-12-31 12:21:41 [INFO] Source 1920x1080 (<=1080p). Preserving source resolution.
|
||||||
|
2025-12-31 12:21:41 [INFO]
|
||||||
|
🧩 ENCODE SETTINGS
|
||||||
|
2025-12-31 12:21:41 [INFO] Video:
|
||||||
|
2025-12-31 12:21:41 [INFO] • Source Resolution: 1920x1080
|
||||||
|
2025-12-31 12:21:41 [INFO] • Target Resolution: 1920x1080
|
||||||
|
2025-12-31 12:21:41 [INFO] • Encoder: av1_nvenc (preset p1, pix_fmt p010le)
|
||||||
|
2025-12-31 12:21:41 [INFO] • Scale Filter: lanczos
|
||||||
|
2025-12-31 12:21:41 [INFO] • Encode Method: CQ
|
||||||
|
2025-12-31 12:21:41 [INFO] • CQ Value: 32
|
||||||
|
2025-12-31 12:21:41 [INFO] Audio Streams (1 detected):
|
||||||
|
2025-12-31 12:21:41 [INFO] - Stream #1: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps
|
||||||
|
2025-12-31 12:21:41 [INFO] Running CQ encode: Innovations in Film - 65mm Black-and-White Film in Oppenheimer -EHX.mkv
|
||||||
|
2025-12-31 12:22:02 [INFO]
|
||||||
|
📊 ENCODE RESULTS:
|
||||||
|
2025-12-31 12:22:02 [INFO] Original Size: 83.83 MB
|
||||||
|
2025-12-31 12:22:02 [INFO] Encoded Size: 110.80 MB
|
||||||
|
2025-12-31 12:22:02 [INFO] Reduction: 132.2% of original (-32.2% saved)
|
||||||
|
2025-12-31 12:22:02 [INFO] Resolution: 1920x1080 → 1920x1080
|
||||||
|
2025-12-31 12:22:02 [INFO] Audio Streams: 1 streams processed
|
||||||
|
2025-12-31 12:22:02 [WARNING] CQ encode failed target (132.2%). Switching to Bitrate for remaining files.
|
||||||
|
2025-12-31 12:22:02 [INFO]
|
||||||
|
🧩 ENCODE SETTINGS
|
||||||
|
2025-12-31 12:22:02 [INFO] Video:
|
||||||
|
2025-12-31 12:22:02 [INFO] • Source Resolution: 1920x1080
|
||||||
|
2025-12-31 12:22:02 [INFO] • Target Resolution: 1920x1080
|
||||||
|
2025-12-31 12:22:02 [INFO] • Encoder: av1_nvenc (preset p1, pix_fmt p010le)
|
||||||
|
2025-12-31 12:22:02 [INFO] • Scale Filter: lanczos
|
||||||
|
2025-12-31 12:22:02 [INFO] • Encode Method: Bitrate
|
||||||
|
2025-12-31 12:22:02 [INFO] • Bitrate: 1500k, Max: 1750k
|
||||||
|
2025-12-31 12:22:02 [INFO] Audio Streams (1 detected):
|
||||||
|
2025-12-31 12:22:02 [INFO] - Stream #1: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps
|
||||||
|
2025-12-31 12:22:02 [INFO] Running Bitrate encode: Innovations in Film - 65mm Black-and-White Film in Oppenheimer -EHX.mkv
|
||||||
|
2025-12-31 12:22:23 [INFO]
|
||||||
|
📊 ENCODE RESULTS:
|
||||||
|
2025-12-31 12:22:23 [INFO] Original Size: 83.83 MB
|
||||||
|
2025-12-31 12:22:23 [INFO] Encoded Size: 110.46 MB
|
||||||
|
2025-12-31 12:22:23 [INFO] Reduction: 131.8% of original (-31.8% saved)
|
||||||
|
2025-12-31 12:22:23 [INFO] Resolution: 1920x1080 → 1920x1080
|
||||||
|
2025-12-31 12:22:23 [INFO] Audio Streams: 1 streams processed
|
||||||
|
2025-12-31 12:22:23 [ERROR] Bitrate encode failed target (131.8%). Stopping process.
|
||||||
|
{"timestamp": "2025-12-31T17:29:47Z", "level": "ERROR", "message": "Folder not found: P:\\movies\\Pacific Rim (2013)'", "module": "process_manager", "funcName": "process_folder", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T17:29:54Z", "level": "INFO", "message": "Processing: Pacific Rim (2013) x265 AAC 7.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 84}
|
||||||
|
{"timestamp": "2025-12-31T17:30:41Z", "level": "INFO", "message": "Copied Pacific Rim (2013) x265 AAC 7.1 Bluray-1080p Tigole.mkv → Pacific Rim (2013) x265 AAC 7.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 89}
|
||||||
|
{"timestamp": "2025-12-31T17:30:42Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T17:30:42Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T17:30:42Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T17:30:51Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T17:30:51Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T17:30:51Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T17:30:51Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T17:30:51Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T17:30:51Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T17:30:51Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T17:30:51Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T17:30:51Z", "level": "INFO", "message": " Audio Streams (2 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T17:30:51Z", "level": "INFO", "message": " - Stream #1: 8ch→6ch | Lang: und | Detected: 623kbps | Action: ENCODE | Target: 448kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T17:30:51Z", "level": "INFO", "message": " - Stream #2: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T17:30:51Z", "level": "INFO", "message": "Running CQ encode: Pacific Rim (2013) x265 AAC 7.1 Bluray-1080p Tigole -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T17:42:36Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T17:42:36Z", "level": "INFO", "message": " Original Size: 5624.98 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T17:42:36Z", "level": "INFO", "message": " Encoded Size: 4029.53 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T17:42:36Z", "level": "INFO", "message": " Reduction: 71.6% of original (28.4% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T17:42:36Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T17:42:36Z", "level": "INFO", "message": " Audio Streams: 2 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T17:42:37Z", "level": "INFO", "message": "Processing: Behind the Scenes - Drift Space.mkv", "module": "process_manager", "funcName": "process_folder", "line": 84}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": "Copied Behind the Scenes - Drift Space.mkv → Behind the Scenes - Drift Space.mkv", "module": "process_manager", "funcName": "process_folder", "line": 89}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T17:42:38Z", "level": "INFO", "message": "Running CQ encode: Behind the Scenes - Drift Space -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T17:42:54Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T17:42:54Z", "level": "INFO", "message": " Original Size: 64.77 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T17:42:54Z", "level": "INFO", "message": " Encoded Size: 79.36 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T17:42:54Z", "level": "INFO", "message": " Reduction: 122.5% of original (-22.5% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T17:42:54Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T17:42:54Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T17:42:54Z", "level": "INFO", "message": "Processing: Behind the Scenes - The Digital Artistry of Pacific Rim.mkv", "module": "process_manager", "funcName": "process_folder", "line": 84}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": "Copied Behind the Scenes - The Digital Artistry of Pacific Rim.mkv → Behind the Scenes - The Digital Artistry of Pacific Rim.mkv", "module": "process_manager", "funcName": "process_folder", "line": 89}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T17:42:56Z", "level": "INFO", "message": "Running CQ encode: Behind the Scenes - The Digital Artistry of Pacific Rim -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T17:43:37Z", "level": "INFO", "message": "Using path as-is: P:\\movies\\Pacific Rim (2013)", "module": "main", "funcName": "normalize_input_path", "line": 49}
|
||||||
|
{"timestamp": "2025-12-31T17:43:37Z", "level": "INFO", "message": "Processing: Pacific Rim (2013) x265 AAC 7.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T17:44:25Z", "level": "INFO", "message": "Copied Pacific Rim (2013) x265 AAC 7.1 Bluray-1080p Tigole.mkv → Pacific Rim (2013) x265 AAC 7.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T17:44:25Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T17:44:25Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T17:44:25Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T17:44:32Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T17:44:32Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T17:44:32Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T17:44:32Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T17:44:32Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T17:44:32Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T17:44:32Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T17:44:32Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T17:44:32Z", "level": "INFO", "message": " Audio Streams (2 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T17:44:32Z", "level": "INFO", "message": " - Stream #1: 8ch→6ch | Lang: und | Detected: 617kbps | Action: ENCODE | Target: 448kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T17:44:32Z", "level": "INFO", "message": " - Stream #2: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T17:44:32Z", "level": "INFO", "message": "Running CQ encode: Pacific Rim (2013) x265 AAC 7.1 Bluray-1080p Tigole -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T18:01:19Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T18:01:19Z", "level": "INFO", "message": " Original Size: 5624.98 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T18:01:19Z", "level": "INFO", "message": " Encoded Size: 4029.53 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T18:01:19Z", "level": "INFO", "message": " Reduction: 71.6% of original (28.4% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T18:01:19Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T18:01:19Z", "level": "INFO", "message": " Audio Streams: 2 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T18:01:57Z", "level": "INFO", "message": "Moved Pacific Rim (2013) x265 AAC 7.1 Bluray-1080p Tigole -EHX.mkv → Pacific Rim (2013) x265 AAC 7.1 Bluray-1080p Tigole -EHX.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 346}
|
||||||
|
{"timestamp": "2025-12-31T18:02:04Z", "level": "INFO", "message": "\n✅ CONVERSION COMPLETE: Pacific Rim (2013) x265 AAC 7.1 Bluray-1080p Tigole -EHX.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 386}
|
||||||
|
{"timestamp": "2025-12-31T18:02:04Z", "level": "INFO", "message": " Type: MOVIE | Show: N/A", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 387}
|
||||||
|
{"timestamp": "2025-12-31T18:02:04Z", "level": "INFO", "message": " Size: 5624.98MB → 4029.53MB (71.6% of original, 28.4% reduction)", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 388}
|
||||||
|
{"timestamp": "2025-12-31T18:02:04Z", "level": "INFO", "message": " Method: CQ | Status: SUCCESS", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 389}
|
||||||
|
{"timestamp": "2025-12-31T18:02:05Z", "level": "INFO", "message": "Deleted original and processing copy for Pacific Rim (2013) x265 AAC 7.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 395}
|
||||||
|
{"timestamp": "2025-12-31T18:02:05Z", "level": "INFO", "message": "Processing: Behind the Scenes - Drift Space.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": "Copied Behind the Scenes - Drift Space.mkv → Behind the Scenes - Drift Space.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T18:02:06Z", "level": "INFO", "message": "Running CQ encode: Behind the Scenes - Drift Space -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T18:02:27Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T18:02:27Z", "level": "INFO", "message": " Original Size: 64.77 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T18:02:27Z", "level": "INFO", "message": " Encoded Size: 79.36 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T18:02:27Z", "level": "INFO", "message": " Reduction: 122.5% of original (-22.5% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T18:02:27Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T18:02:27Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T18:02:27Z", "level": "INFO", "message": "Processing: Behind the Scenes - The Digital Artistry of Pacific Rim.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": "Copied Behind the Scenes - The Digital Artistry of Pacific Rim.mkv → Behind the Scenes - The Digital Artistry of Pacific Rim.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T18:02:29Z", "level": "INFO", "message": "Running CQ encode: Behind the Scenes - The Digital Artistry of Pacific Rim -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T18:03:26Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T18:03:26Z", "level": "INFO", "message": " Original Size: 207.39 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T18:03:26Z", "level": "INFO", "message": " Encoded Size: 375.19 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T18:03:26Z", "level": "INFO", "message": " Reduction: 180.9% of original (-80.9% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T18:03:26Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T18:03:26Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T18:03:26Z", "level": "INFO", "message": "Processing: Behind the Scenes - The Shatterdome.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T18:03:27Z", "level": "INFO", "message": "Copied Behind the Scenes - The Shatterdome.mkv → Behind the Scenes - The Shatterdome.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T18:03:28Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:03:28Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T18:03:28Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T18:03:28Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T18:03:28Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T18:03:28Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:03:28Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T18:03:28Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T18:03:28Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T18:03:28Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T18:03:28Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T18:03:28Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T18:03:28Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T18:03:28Z", "level": "INFO", "message": "Running CQ encode: Behind the Scenes - The Shatterdome -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T18:04:07Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T18:04:07Z", "level": "INFO", "message": " Original Size: 116.78 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T18:04:07Z", "level": "INFO", "message": " Encoded Size: 203.90 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T18:04:07Z", "level": "INFO", "message": " Reduction: 174.6% of original (-74.6% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T18:04:07Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T18:04:07Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T18:04:07Z", "level": "ERROR", "message": "3 consecutive failures. Stopping process.", "module": "process_manager", "funcName": "process_folder", "line": 189}
|
||||||
|
{"timestamp": "2025-12-31T18:04:07Z", "level": "INFO", "message": "Batch processing complete", "module": "process_manager", "funcName": "process_folder", "line": 336}
|
||||||
|
{"timestamp": "2025-12-31T18:13:31Z", "level": "INFO", "message": "Using path as-is: P:\\movies\\Planes, Trains & Automobiles (1987)", "module": "main", "funcName": "normalize_input_path", "line": 49}
|
||||||
|
{"timestamp": "2025-12-31T18:13:31Z", "level": "INFO", "message": "Processing: Planes, Trains and Automobiles (1987) x265 AAC 5.1 Bluray-1080p afm72.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T18:14:10Z", "level": "INFO", "message": "Copied Planes, Trains and Automobiles (1987) x265 AAC 5.1 Bluray-1080p afm72.mkv → Planes, Trains and Automobiles (1987) x265 AAC 5.1 Bluray-1080p afm72.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T18:14:10Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:14:10Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T18:14:10Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T18:14:12Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T18:14:12Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T18:14:12Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:14:12Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T18:14:12Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T18:14:12Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T18:14:12Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T18:14:12Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T18:14:12Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T18:14:12Z", "level": "INFO", "message": " - Stream #1: 6ch→6ch | Lang: und | Detected: 445kbps | Action: ENCODE | Target: 384kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T18:14:12Z", "level": "INFO", "message": "Running CQ encode: Planes, Trains and Automobiles (1987) x265 AAC 5.1 Bluray-1080p afm72 -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T18:20:53Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T18:20:53Z", "level": "INFO", "message": " Original Size: 4489.88 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T18:20:53Z", "level": "INFO", "message": " Encoded Size: 2513.95 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T18:20:53Z", "level": "INFO", "message": " Reduction: 56.0% of original (44.0% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T18:20:53Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T18:20:53Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T18:21:15Z", "level": "INFO", "message": "Moved Planes, Trains and Automobiles (1987) x265 AAC 5.1 Bluray-1080p afm72 -EHX.mkv → Planes, Trains and Automobiles (1987) x265 AAC 5.1 Bluray-1080p afm72 -EHX.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 346}
|
||||||
|
{"timestamp": "2025-12-31T18:21:21Z", "level": "INFO", "message": "\n✅ CONVERSION COMPLETE: Planes, Trains and Automobiles (1987) x265 AAC 5.1 Bluray-1080p afm72 -EHX.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 386}
|
||||||
|
{"timestamp": "2025-12-31T18:21:21Z", "level": "INFO", "message": " Type: MOVIE | Show: N/A", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 387}
|
||||||
|
{"timestamp": "2025-12-31T18:21:21Z", "level": "INFO", "message": " Size: 4489.88MB → 2513.95MB (56.0% of original, 44.0% reduction)", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 388}
|
||||||
|
{"timestamp": "2025-12-31T18:21:21Z", "level": "INFO", "message": " Method: CQ | Status: SUCCESS", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 389}
|
||||||
|
{"timestamp": "2025-12-31T18:21:22Z", "level": "INFO", "message": "Deleted original and processing copy for Planes, Trains and Automobiles (1987) x265 AAC 5.1 Bluray-1080p afm72.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 395}
|
||||||
|
{"timestamp": "2025-12-31T18:21:22Z", "level": "INFO", "message": "Batch processing complete", "module": "process_manager", "funcName": "process_folder", "line": 336}
|
||||||
|
{"timestamp": "2025-12-31T18:21:22Z", "level": "INFO", "message": "Using path as-is: \\mnt\\plex\\movies\\Hackers (1995)", "module": "main", "funcName": "normalize_input_path", "line": 49}
|
||||||
|
{"timestamp": "2025-12-31T18:21:22Z", "level": "ERROR", "message": "Folder not found: \\mnt\\plex\\movies\\Hackers (1995)", "module": "main", "funcName": "main", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T18:21:22Z", "level": "INFO", "message": "Using path as-is: \\mnt\\plex\\movies\\Bullet Train (2022)", "module": "main", "funcName": "normalize_input_path", "line": 49}
|
||||||
|
{"timestamp": "2025-12-31T18:21:22Z", "level": "ERROR", "message": "Folder not found: \\mnt\\plex\\movies\\Bullet Train (2022)", "module": "main", "funcName": "main", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T18:21:23Z", "level": "INFO", "message": "Using path as-is: \\mnt\\plex\\movies\\The Truman Show (1998)", "module": "main", "funcName": "normalize_input_path", "line": 49}
|
||||||
|
{"timestamp": "2025-12-31T18:21:23Z", "level": "ERROR", "message": "Folder not found: \\mnt\\plex\\movies\\The Truman Show (1998)", "module": "main", "funcName": "main", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T18:25:45Z", "level": "INFO", "message": "Using path as-is: P:\\movies\\Planes, Trains & Automobiles (1987)", "module": "main", "funcName": "normalize_input_path", "line": 49}
|
||||||
|
{"timestamp": "2025-12-31T18:25:45Z", "level": "INFO", "message": "Skipping: Planes, Trains and Automobiles (1987) AV1 AAC 5.1 Bluray-1080p EHX.mkv", "module": "process_manager", "funcName": "process_folder", "line": 97}
|
||||||
|
{"timestamp": "2025-12-31T18:25:45Z", "level": "INFO", "message": "Batch processing complete", "module": "process_manager", "funcName": "process_folder", "line": 336}
|
||||||
|
{"timestamp": "2025-12-31T18:25:46Z", "level": "INFO", "message": "Path mapping: /mnt/plex/movies/Hackers (1995) -> P:\\movies\\Hackers (1995)", "module": "main", "funcName": "normalize_input_path", "line": 41}
|
||||||
|
{"timestamp": "2025-12-31T18:25:46Z", "level": "INFO", "message": "Processing: Hackers (1995) x265 AAC 5.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T18:26:34Z", "level": "INFO", "message": "Copied Hackers (1995) x265 AAC 5.1 Bluray-1080p Tigole.mkv → Hackers (1995) x265 AAC 5.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T18:26:34Z", "level": "INFO", "message": "Source resolution detected: 1920x804", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:26:34Z", "level": "INFO", "message": "Source 1920x804 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T18:26:34Z", "level": "INFO", "message": "Source 1920x804 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T18:26:40Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T18:26:40Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T18:26:40Z", "level": "INFO", "message": " • Source Resolution: 1920x804", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:26:40Z", "level": "INFO", "message": " • Target Resolution: 1920x804", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T18:26:40Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T18:26:40Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T18:26:40Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T18:26:40Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T18:26:40Z", "level": "INFO", "message": " Audio Streams (2 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T18:26:40Z", "level": "INFO", "message": " - Stream #1: 6ch→6ch | Lang: und | Detected: 511kbps | Action: ENCODE | Target: 448kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T18:26:40Z", "level": "INFO", "message": " - Stream #2: 2ch→2ch | Lang: und | Detected: 72kbps | Action: COPY (preserve) | Target: 72kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T18:26:40Z", "level": "INFO", "message": "Running CQ encode: Hackers (1995) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T18:37:33Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T18:37:33Z", "level": "INFO", "message": " Original Size: 5601.42 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T18:37:33Z", "level": "INFO", "message": " Encoded Size: 2346.49 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T18:37:33Z", "level": "INFO", "message": " Reduction: 41.9% of original (58.1% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T18:37:33Z", "level": "INFO", "message": " Resolution: 1920x804 → 1920x804", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T18:37:33Z", "level": "INFO", "message": " Audio Streams: 2 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T18:37:54Z", "level": "INFO", "message": "Moved Hackers (1995) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv → Hackers (1995) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 346}
|
||||||
|
{"timestamp": "2025-12-31T18:38:00Z", "level": "INFO", "message": "\n✅ CONVERSION COMPLETE: Hackers (1995) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 386}
|
||||||
|
{"timestamp": "2025-12-31T18:38:00Z", "level": "INFO", "message": " Type: MOVIE | Show: N/A", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 387}
|
||||||
|
{"timestamp": "2025-12-31T18:38:00Z", "level": "INFO", "message": " Size: 5601.42MB → 2346.49MB (41.9% of original, 58.1% reduction)", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 388}
|
||||||
|
{"timestamp": "2025-12-31T18:38:00Z", "level": "INFO", "message": " Method: CQ | Status: SUCCESS", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 389}
|
||||||
|
{"timestamp": "2025-12-31T18:38:01Z", "level": "INFO", "message": "Deleted original and processing copy for Hackers (1995) x265 AAC 5.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 395}
|
||||||
|
{"timestamp": "2025-12-31T18:38:01Z", "level": "INFO", "message": "Processing: The Keyboard Cowboys - A Look Back at Hackers.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T18:38:07Z", "level": "INFO", "message": "Copied The Keyboard Cowboys - A Look Back at Hackers.mkv → The Keyboard Cowboys - A Look Back at Hackers.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T18:38:07Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:38:07Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T18:38:07Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T18:38:08Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T18:38:08Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T18:38:08Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:38:08Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T18:38:08Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T18:38:08Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T18:38:08Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T18:38:08Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T18:38:08Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T18:38:08Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 67kbps | Action: COPY (preserve) | Target: 67kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T18:38:08Z", "level": "INFO", "message": "Running CQ encode: The Keyboard Cowboys - A Look Back at Hackers -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T18:40:45Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T18:40:45Z", "level": "INFO", "message": " Original Size: 641.76 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T18:40:45Z", "level": "INFO", "message": " Encoded Size: 866.95 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T18:40:45Z", "level": "INFO", "message": " Reduction: 135.1% of original (-35.1% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T18:40:45Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T18:40:45Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T18:40:45Z", "level": "INFO", "message": "Processing: Trailer.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T18:40:45Z", "level": "INFO", "message": "Copied Trailer.mkv → Trailer.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T18:40:45Z", "level": "INFO", "message": "Source resolution detected: 1920x848", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:40:45Z", "level": "INFO", "message": "Source 1920x848 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T18:40:45Z", "level": "INFO", "message": "Source 1920x848 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T18:40:46Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T18:40:46Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T18:40:46Z", "level": "INFO", "message": " • Source Resolution: 1920x848", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:40:46Z", "level": "INFO", "message": " • Target Resolution: 1920x848", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T18:40:46Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T18:40:46Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T18:40:46Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T18:40:46Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T18:40:46Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T18:40:46Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 67kbps | Action: COPY (preserve) | Target: 67kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T18:40:46Z", "level": "INFO", "message": "Running CQ encode: Trailer -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T18:40:54Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T18:40:54Z", "level": "INFO", "message": " Original Size: 39.28 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T18:40:54Z", "level": "INFO", "message": " Encoded Size: 48.99 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T18:40:54Z", "level": "INFO", "message": " Reduction: 124.7% of original (-24.7% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T18:40:54Z", "level": "INFO", "message": " Resolution: 1920x848 → 1920x848", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T18:40:54Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T18:40:54Z", "level": "INFO", "message": "Batch processing complete", "module": "process_manager", "funcName": "process_folder", "line": 336}
|
||||||
|
{"timestamp": "2025-12-31T18:40:55Z", "level": "INFO", "message": "Path mapping: /mnt/plex/movies/Bullet Train (2022) -> P:\\movies\\Bullet Train (2022)", "module": "main", "funcName": "normalize_input_path", "line": 41}
|
||||||
|
{"timestamp": "2025-12-31T18:40:55Z", "level": "INFO", "message": "Processing: Bullet Train (2022) x265 AAC 5.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T18:41:44Z", "level": "INFO", "message": "Copied Bullet Train (2022) x265 AAC 5.1 Bluray-1080p Tigole.mkv → Bullet Train (2022) x265 AAC 5.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T18:41:44Z", "level": "INFO", "message": "Source resolution detected: 1920x804", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:41:44Z", "level": "INFO", "message": "Source 1920x804 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T18:41:44Z", "level": "INFO", "message": "Source 1920x804 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T18:41:52Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T18:41:52Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T18:41:52Z", "level": "INFO", "message": " • Source Resolution: 1920x804", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:41:52Z", "level": "INFO", "message": " • Target Resolution: 1920x804", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T18:41:52Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T18:41:52Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T18:41:52Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T18:41:52Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T18:41:52Z", "level": "INFO", "message": " Audio Streams (2 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T18:41:52Z", "level": "INFO", "message": " - Stream #1: 6ch→6ch | Lang: und | Detected: 391kbps | Action: ENCODE | Target: 384kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T18:41:52Z", "level": "INFO", "message": " - Stream #2: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T18:41:52Z", "level": "INFO", "message": "Running CQ encode: Bullet Train (2022) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T18:50:44Z", "level": "INFO", "message": "Added to paths.txt: --r 720 --m bitrate \"P:\\movies\\The Lord of the Rings - The Fellowship of the Ring - Extended Edition (2001)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 317}
|
||||||
|
{"timestamp": "2025-12-31T18:50:45Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T18:50:45Z", "level": "INFO", "message": " Original Size: 5857.37 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T18:50:45Z", "level": "INFO", "message": " Encoded Size: 2444.68 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T18:50:45Z", "level": "INFO", "message": " Reduction: 41.7% of original (58.3% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T18:50:45Z", "level": "INFO", "message": " Resolution: 1920x804 → 1920x804", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T18:50:45Z", "level": "INFO", "message": " Audio Streams: 2 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T18:51:07Z", "level": "INFO", "message": "Moved Bullet Train (2022) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv → Bullet Train (2022) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 346}
|
||||||
|
{"timestamp": "2025-12-31T18:51:18Z", "level": "INFO", "message": "\n✅ CONVERSION COMPLETE: Bullet Train (2022) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 386}
|
||||||
|
{"timestamp": "2025-12-31T18:51:18Z", "level": "INFO", "message": " Type: MOVIE | Show: N/A", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 387}
|
||||||
|
{"timestamp": "2025-12-31T18:51:18Z", "level": "INFO", "message": " Size: 5857.37MB → 2444.68MB (41.7% of original, 58.3% reduction)", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 388}
|
||||||
|
{"timestamp": "2025-12-31T18:51:18Z", "level": "INFO", "message": " Method: CQ | Status: SUCCESS", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 389}
|
||||||
|
{"timestamp": "2025-12-31T18:51:19Z", "level": "INFO", "message": "Deleted original and processing copy for Bullet Train (2022) x265 AAC 5.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 395}
|
||||||
|
{"timestamp": "2025-12-31T18:51:19Z", "level": "INFO", "message": "Batch processing complete", "module": "process_manager", "funcName": "process_folder", "line": 336}
|
||||||
|
{"timestamp": "2025-12-31T18:51:19Z", "level": "INFO", "message": "Path mapping: /mnt/plex/movies/The Truman Show (1998) -> P:\\movies\\The Truman Show (1998)", "module": "main", "funcName": "normalize_input_path", "line": 41}
|
||||||
|
{"timestamp": "2025-12-31T18:51:19Z", "level": "INFO", "message": "Processing: The Truman Show (1998) x265 AAC 5.1 Bluray-1080p Silence.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T18:52:03Z", "level": "INFO", "message": "Copied The Truman Show (1998) x265 AAC 5.1 Bluray-1080p Silence.mkv → The Truman Show (1998) x265 AAC 5.1 Bluray-1080p Silence.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T18:52:03Z", "level": "INFO", "message": "Source resolution detected: 1918x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:52:03Z", "level": "INFO", "message": "Source 1918x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T18:52:03Z", "level": "INFO", "message": "Source 1918x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T18:52:10Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T18:52:10Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T18:52:10Z", "level": "INFO", "message": " • Source Resolution: 1918x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T18:52:10Z", "level": "INFO", "message": " • Target Resolution: 1918x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T18:52:10Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T18:52:10Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T18:52:10Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T18:52:10Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T18:52:10Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T18:52:10Z", "level": "INFO", "message": " - Stream #1: 6ch→6ch | Lang: und | Detected: 592kbps | Action: ENCODE | Target: 448kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T18:52:10Z", "level": "INFO", "message": "Running CQ encode: The Truman Show (1998) x265 AAC 5.1 Bluray-1080p Silence -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T18:54:22Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\The Lord of the Rings - The Return of the King - Extended Edition (2003)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 323}
|
||||||
|
{"timestamp": "2025-12-31T18:55:54Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\The Lord of the Rings - The Return of the King - Extended Edition (2003)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 323}
|
||||||
|
{"timestamp": "2025-12-31T19:01:20Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T19:01:20Z", "level": "INFO", "message": " Original Size: 5152.45 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T19:01:20Z", "level": "INFO", "message": " Encoded Size: 2971.12 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T19:01:20Z", "level": "INFO", "message": " Reduction: 57.7% of original (42.3% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T19:01:20Z", "level": "INFO", "message": " Resolution: 1918x1080 → 1918x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T19:01:20Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T19:01:47Z", "level": "INFO", "message": "Moved The Truman Show (1998) x265 AAC 5.1 Bluray-1080p Silence -EHX.mkv → The Truman Show (1998) x265 AAC 5.1 Bluray-1080p Silence -EHX.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 346}
|
||||||
|
{"timestamp": "2025-12-31T19:01:53Z", "level": "INFO", "message": "\n✅ CONVERSION COMPLETE: The Truman Show (1998) x265 AAC 5.1 Bluray-1080p Silence -EHX.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 386}
|
||||||
|
{"timestamp": "2025-12-31T19:01:53Z", "level": "INFO", "message": " Type: MOVIE | Show: N/A", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 387}
|
||||||
|
{"timestamp": "2025-12-31T19:01:53Z", "level": "INFO", "message": " Size: 5152.45MB → 2971.12MB (57.7% of original, 42.3% reduction)", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 388}
|
||||||
|
{"timestamp": "2025-12-31T19:01:53Z", "level": "INFO", "message": " Method: CQ | Status: SUCCESS", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 389}
|
||||||
|
{"timestamp": "2025-12-31T19:01:54Z", "level": "INFO", "message": "Deleted original and processing copy for The Truman Show (1998) x265 AAC 5.1 Bluray-1080p Silence.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 395}
|
||||||
|
{"timestamp": "2025-12-31T19:01:54Z", "level": "INFO", "message": "Processing: The Making of The Truman Show.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T19:01:56Z", "level": "INFO", "message": "Copied The Making of The Truman Show.mkv → The Making of The Truman Show.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T19:01:56Z", "level": "INFO", "message": "Source resolution detected: 720x480", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:01:56Z", "level": "INFO", "message": "Source 720x480 (<=720p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 65}
|
||||||
|
{"timestamp": "2025-12-31T19:01:56Z", "level": "INFO", "message": "Source 720x480 (<=720p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 124}
|
||||||
|
{"timestamp": "2025-12-31T19:01:58Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T19:01:58Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T19:01:58Z", "level": "INFO", "message": " • Source Resolution: 720x480", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:01:58Z", "level": "INFO", "message": " • Target Resolution: 720x480", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T19:01:58Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T19:01:58Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T19:01:58Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T19:01:58Z", "level": "INFO", "message": " • CQ Value: 34", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T19:01:58Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T19:01:58Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 81kbps | Action: COPY (preserve) | Target: 81kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T19:01:58Z", "level": "INFO", "message": "Running CQ encode: The Making of The Truman Show -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T19:02:32Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T19:02:32Z", "level": "INFO", "message": " Original Size: 172.27 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T19:02:32Z", "level": "INFO", "message": " Encoded Size: 202.00 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T19:02:32Z", "level": "INFO", "message": " Reduction: 117.3% of original (-17.3% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T19:02:32Z", "level": "INFO", "message": " Resolution: 720x480 → 720x480", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T19:02:32Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T19:02:32Z", "level": "INFO", "message": "Processing: The Visual Effects of The Truman Show.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": "Copied The Visual Effects of The Truman Show.mkv → The Visual Effects of The Truman Show.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": "Source resolution detected: 720x480", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": "Source 720x480 (<=720p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 65}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": "Source 720x480 (<=720p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 124}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": " • Source Resolution: 720x480", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": " • Target Resolution: 720x480", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": " • CQ Value: 34", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 81kbps | Action: COPY (preserve) | Target: 81kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T19:02:33Z", "level": "INFO", "message": "Running CQ encode: The Visual Effects of The Truman Show -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T19:02:45Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T19:02:45Z", "level": "INFO", "message": " Original Size: 63.59 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T19:02:45Z", "level": "INFO", "message": " Encoded Size: 67.60 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T19:02:45Z", "level": "INFO", "message": " Reduction: 106.3% of original (-6.3% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T19:02:45Z", "level": "INFO", "message": " Resolution: 720x480 → 720x480", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T19:02:45Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T19:02:45Z", "level": "INFO", "message": "Processing: Product Placement.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": "Copied Product Placement.mkv → Product Placement.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": "Source resolution detected: 720x480", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": "Source 720x480 (<=720p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 65}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": "Source 720x480 (<=720p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 124}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": " • Source Resolution: 720x480", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": " • Target Resolution: 720x480", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": " • CQ Value: 34", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 81kbps | Action: COPY (preserve) | Target: 81kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T19:02:46Z", "level": "INFO", "message": "Running CQ encode: Product Placement -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T19:02:51Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T19:02:51Z", "level": "INFO", "message": " Original Size: 26.56 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T19:02:51Z", "level": "INFO", "message": " Encoded Size: 28.37 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T19:02:51Z", "level": "INFO", "message": " Reduction: 106.8% of original (-6.8% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T19:02:51Z", "level": "INFO", "message": " Resolution: 720x480 → 720x480", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T19:02:51Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T19:02:51Z", "level": "ERROR", "message": "3 consecutive failures. Stopping process.", "module": "process_manager", "funcName": "process_folder", "line": 189}
|
||||||
|
{"timestamp": "2025-12-31T19:02:51Z", "level": "INFO", "message": "Batch processing complete", "module": "process_manager", "funcName": "process_folder", "line": 336}
|
||||||
|
{"timestamp": "2025-12-31T19:05:59Z", "level": "INFO", "message": "paths.txt cleared", "module": "gui_path_manager", "funcName": "_clear_paths_file", "line": 363}
|
||||||
|
{"timestamp": "2025-12-31T19:08:45Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\John Wick - Chapter 4 (2023)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 323}
|
||||||
|
{"timestamp": "2025-12-31T19:08:55Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\The Lord of the Rings - The Return of the King - Extended Edition (2003)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 323}
|
||||||
|
{"timestamp": "2025-12-31T19:08:58Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\F1 (2025)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 323}
|
||||||
|
{"timestamp": "2025-12-31T19:09:00Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\John Wick - Chapter 3 - Parabellum (2019)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 323}
|
||||||
|
{"timestamp": "2025-12-31T19:09:03Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\Starship Troopers (1997)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 323}
|
||||||
|
{"timestamp": "2025-12-31T19:09:31Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\Ferris Bueller's Day Off (1986)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 323}
|
||||||
|
{"timestamp": "2025-12-31T19:09:47Z", "level": "INFO", "message": "Using path as-is: P:\\movies\\John Wick - Chapter 4 (2023)", "module": "main", "funcName": "normalize_input_path", "line": 49}
|
||||||
|
{"timestamp": "2025-12-31T19:09:47Z", "level": "INFO", "message": "Processing: John Wick - Chapter 4 (2023) x265 AAC 7.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T19:10:57Z", "level": "INFO", "message": "Copied John Wick - Chapter 4 (2023) x265 AAC 7.1 Bluray-1080p Tigole.mkv → John Wick - Chapter 4 (2023) x265 AAC 7.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T19:10:57Z", "level": "INFO", "message": "Source resolution detected: 1920x804", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:10:57Z", "level": "INFO", "message": "Source 1920x804 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T19:10:57Z", "level": "INFO", "message": "Source 1920x804 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T19:11:09Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T19:11:09Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T19:11:09Z", "level": "INFO", "message": " • Source Resolution: 1920x804", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:11:09Z", "level": "INFO", "message": " • Target Resolution: 1920x804", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T19:11:09Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T19:11:09Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T19:11:09Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T19:11:09Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T19:11:09Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T19:11:09Z", "level": "INFO", "message": " - Stream #1: 8ch→6ch | Lang: und | Detected: 599kbps | Action: ENCODE | Target: 448kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T19:11:09Z", "level": "INFO", "message": "Running CQ encode: John Wick - Chapter 4 (2023) x265 AAC 7.1 Bluray-1080p Tigole -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T19:18:49Z", "level": "INFO", "message": "Added to paths.txt: \"P:\\movies\\Furious 7 (2015)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 452}
|
||||||
|
{"timestamp": "2025-12-31T19:19:12Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\Furious 7 (2015)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 452}
|
||||||
|
{"timestamp": "2025-12-31T19:19:36Z", "level": "INFO", "message": "Added to paths.txt: \"P:\\movies\\Pirates of the Caribbean - The Curse of the Black Pearl (2003)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 452}
|
||||||
|
{"timestamp": "2025-12-31T19:19:42Z", "level": "INFO", "message": "Added to paths.txt: \"P:\\movies\\Fast & Furious 6 (2013)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 452}
|
||||||
|
{"timestamp": "2025-12-31T19:21:19Z", "level": "INFO", "message": "Removed from paths.txt: P:\\movies\\Pirates of the Caribbean - The Curse of the Black Pearl (2003)", "module": "gui_path_manager", "funcName": "_remove_from_paths_file", "line": 504}
|
||||||
|
{"timestamp": "2025-12-31T19:21:37Z", "level": "INFO", "message": "Removed from paths.txt: P:\\movies\\Fast & Furious 6 (2013)", "module": "gui_path_manager", "funcName": "_remove_from_paths_file", "line": 504}
|
||||||
|
{"timestamp": "2025-12-31T19:21:51Z", "level": "INFO", "message": "Removed from paths.txt: P:\\movies\\Furious 7 (2015)", "module": "gui_path_manager", "funcName": "_remove_from_paths_file", "line": 504}
|
||||||
|
{"timestamp": "2025-12-31T19:21:58Z", "level": "INFO", "message": "Removed from paths.txt: P:\\movies\\Ferris Bueller's Day Off (1986)", "module": "gui_path_manager", "funcName": "_remove_from_paths_file", "line": 504}
|
||||||
|
{"timestamp": "2025-12-31T19:22:07Z", "level": "INFO", "message": "Added to paths.txt: \"P:\\movies\\Ferris Bueller's Day Off (1986)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 461}
|
||||||
|
{"timestamp": "2025-12-31T19:23:37Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T19:23:37Z", "level": "INFO", "message": " Original Size: 8144.41 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T19:23:37Z", "level": "INFO", "message": " Encoded Size: 3288.02 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T19:23:37Z", "level": "INFO", "message": " Reduction: 40.4% of original (59.6% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T19:23:37Z", "level": "INFO", "message": " Resolution: 1920x804 → 1920x804", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T19:23:37Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T19:24:07Z", "level": "INFO", "message": "Moved John Wick - Chapter 4 (2023) x265 AAC 7.1 Bluray-1080p Tigole -EHX.mkv → John Wick - Chapter 4 (2023) x265 AAC 7.1 Bluray-1080p Tigole -EHX.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 346}
|
||||||
|
{"timestamp": "2025-12-31T19:24:16Z", "level": "INFO", "message": "\n✅ CONVERSION COMPLETE: John Wick - Chapter 4 (2023) x265 AAC 7.1 Bluray-1080p Tigole -EHX.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 386}
|
||||||
|
{"timestamp": "2025-12-31T19:24:16Z", "level": "INFO", "message": " Type: MOVIE | Show: N/A", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 387}
|
||||||
|
{"timestamp": "2025-12-31T19:24:16Z", "level": "INFO", "message": " Size: 8144.41MB → 3288.02MB (40.4% of original, 59.6% reduction)", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 388}
|
||||||
|
{"timestamp": "2025-12-31T19:24:16Z", "level": "INFO", "message": " Method: CQ | Status: SUCCESS", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 389}
|
||||||
|
{"timestamp": "2025-12-31T19:24:17Z", "level": "INFO", "message": "Deleted original and processing copy for John Wick - Chapter 4 (2023) x265 AAC 7.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 395}
|
||||||
|
{"timestamp": "2025-12-31T19:24:17Z", "level": "INFO", "message": "Processing: A Shot in the Dark.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": "Copied A Shot in the Dark.mkv → A Shot in the Dark.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T19:24:18Z", "level": "INFO", "message": "Running CQ encode: A Shot in the Dark -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T19:24:35Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T19:24:35Z", "level": "INFO", "message": " Original Size: 120.37 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T19:24:35Z", "level": "INFO", "message": " Encoded Size: 132.51 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T19:24:35Z", "level": "INFO", "message": " Reduction: 110.1% of original (-10.1% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T19:24:35Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T19:24:35Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T19:24:35Z", "level": "INFO", "message": "Processing: Chad and Keanu - Through Wick and Thin.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T19:24:36Z", "level": "INFO", "message": "Copied Chad and Keanu - Through Wick and Thin.mkv → Chad and Keanu - Through Wick and Thin.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T19:24:36Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:24:36Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T19:24:36Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T19:24:37Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T19:24:37Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T19:24:37Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:24:37Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T19:24:37Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T19:24:37Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T19:24:37Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T19:24:37Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T19:24:37Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T19:24:37Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T19:24:37Z", "level": "INFO", "message": "Running CQ encode: Chad and Keanu - Through Wick and Thin -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T19:24:52Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T19:24:52Z", "level": "INFO", "message": " Original Size: 94.32 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T19:24:52Z", "level": "INFO", "message": " Encoded Size: 113.04 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T19:24:52Z", "level": "INFO", "message": " Reduction: 119.8% of original (-19.8% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T19:24:52Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T19:24:52Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T19:24:52Z", "level": "INFO", "message": "Processing: In Honor of the Dead.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": "Copied In Honor of the Dead.mkv → In Honor of the Dead.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T19:24:53Z", "level": "INFO", "message": "Running CQ encode: In Honor of the Dead -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T19:25:02Z", "level": "INFO", "message": "Added to paths.txt: \"P:\\movies\\The Lord of the Rings - The Two Towers - Extended Edition (2002)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 474}
|
||||||
|
{"timestamp": "2025-12-31T19:25:08Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T19:25:08Z", "level": "INFO", "message": " Original Size: 88.34 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T19:25:08Z", "level": "INFO", "message": " Encoded Size: 109.24 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T19:25:08Z", "level": "INFO", "message": " Reduction: 123.7% of original (-23.7% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T19:25:08Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T19:25:08Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T19:25:08Z", "level": "ERROR", "message": "3 consecutive failures. Stopping process.", "module": "process_manager", "funcName": "process_folder", "line": 189}
|
||||||
|
{"timestamp": "2025-12-31T19:25:08Z", "level": "INFO", "message": "Batch processing complete", "module": "process_manager", "funcName": "process_folder", "line": 336}
|
||||||
|
{"timestamp": "2025-12-31T19:25:09Z", "level": "INFO", "message": "Using path as-is: P:\\movies\\The Lord of the Rings - The Return of the King - Extended Edition (2003)", "module": "main", "funcName": "normalize_input_path", "line": 49}
|
||||||
|
{"timestamp": "2025-12-31T19:25:09Z", "level": "INFO", "message": "Processing: The Lord of the Rings - The Return of the King (2003) x265 EAC3 5.1 Bluray-1080p GalaxyRG265.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": "Copied The Lord of the Rings - The Return of the King (2003) x265 EAC3 5.1 Bluray-1080p GalaxyRG265.mkv → The Lord of the Rings - The Return of the King (2003) x265 EAC3 5.1 Bluray-1080p GalaxyRG265.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "WARNING", "message": "Failed to calculate bitrate for stream 0: Command '['ffmpeg', '-y', '-i', 'processing\\\\The Lord of the Rings - The Return of the King (2003) x265 EAC3 5.1 Bluray-1080p GalaxyRG265.mkv', '-map', '0:a:0', '-c', 'copy', 'C:\\\\Users\\\\Tyler\\\\AppData\\\\Local\\\\Temp\\\\tmpswtq225p.aac']' returned non-zero exit status 4294967274.. Will fall back to metadata.", "module": "audio_handler", "funcName": "calculate_stream_bitrate", "line": 74}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": "Stream 1: Using fallback bitrate 384 kbps", "module": "audio_handler", "funcName": "get_audio_streams", "line": 113}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": " - Stream #1: 6ch→6ch | Lang: und | Detected: 384kbps | Action: ENCODE | Target: 384kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T19:26:19Z", "level": "INFO", "message": "Running CQ encode: The Lord of the Rings - The Return of the King (2003) x265 EAC3 5.1 Bluray-1080p GalaxyRG265 -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T19:29:10Z", "level": "INFO", "message": "Loaded cache with 3195 entries", "module": "gui_path_manager", "funcName": "_load_cache", "line": 194}
|
||||||
|
{"timestamp": "2025-12-31T19:34:09Z", "level": "INFO", "message": "Loaded tv cache with 320 entries", "module": "gui_path_manager", "funcName": "_load_cache", "line": 205}
|
||||||
|
{"timestamp": "2025-12-31T19:34:12Z", "level": "INFO", "message": "Loaded movies cache with 2875 entries", "module": "gui_path_manager", "funcName": "_load_cache", "line": 205}
|
||||||
|
{"timestamp": "2025-12-31T19:35:18Z", "level": "INFO", "message": "Loaded tv cache with 320 entries", "module": "gui_path_manager", "funcName": "_load_cache", "line": 210}
|
||||||
|
{"timestamp": "2025-12-31T19:35:25Z", "level": "INFO", "message": "Loaded movies cache with 2875 entries", "module": "gui_path_manager", "funcName": "_load_cache", "line": 210}
|
||||||
|
{"timestamp": "2025-12-31T19:35:58Z", "level": "INFO", "message": "Loaded tv cache with 320 entries", "module": "gui_path_manager", "funcName": "_load_cache", "line": 210}
|
||||||
|
{"timestamp": "2025-12-31T19:36:09Z", "level": "INFO", "message": "Loaded movies cache with 2875 entries", "module": "gui_path_manager", "funcName": "_load_cache", "line": 210}
|
||||||
|
{"timestamp": "2025-12-31T19:37:07Z", "level": "INFO", "message": "Loaded tv cache with 320 entries", "module": "gui_path_manager", "funcName": "_load_cache", "line": 210}
|
||||||
|
{"timestamp": "2025-12-31T19:37:09Z", "level": "INFO", "message": "Loaded anime cache with 404 entries", "module": "gui_path_manager", "funcName": "_load_cache", "line": 210}
|
||||||
|
{"timestamp": "2025-12-31T19:37:13Z", "level": "INFO", "message": "Loaded tv cache with 320 entries", "module": "gui_path_manager", "funcName": "_load_cache", "line": 210}
|
||||||
|
{"timestamp": "2025-12-31T19:37:19Z", "level": "INFO", "message": "Cache file exists for tv", "module": "gui_path_manager", "funcName": "_load_cache", "line": 210}
|
||||||
|
{"timestamp": "2025-12-31T19:37:26Z", "level": "INFO", "message": "Cache file exists for movies", "module": "gui_path_manager", "funcName": "_load_cache", "line": 210}
|
||||||
|
{"timestamp": "2025-12-31T19:37:41Z", "level": "INFO", "message": "Cache file exists for tv", "module": "gui_path_manager", "funcName": "_load_cache", "line": 210}
|
||||||
|
{"timestamp": "2025-12-31T19:37:43Z", "level": "INFO", "message": "Cache file exists for movies", "module": "gui_path_manager", "funcName": "_load_cache", "line": 210}
|
||||||
|
{"timestamp": "2025-12-31T19:44:59Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T19:44:59Z", "level": "INFO", "message": " Original Size: 8033.94 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T19:44:59Z", "level": "INFO", "message": " Encoded Size: 6350.02 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T19:44:59Z", "level": "INFO", "message": " Reduction: 79.0% of original (21.0% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T19:44:59Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T19:44:59Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T19:45:01Z", "level": "INFO", "message": "Batch processing complete", "module": "process_manager", "funcName": "process_folder", "line": 336}
|
||||||
|
{"timestamp": "2025-12-31T19:45:01Z", "level": "INFO", "message": "Using path as-is: P:\\movies\\F1 (2025)", "module": "main", "funcName": "normalize_input_path", "line": 49}
|
||||||
|
{"timestamp": "2025-12-31T19:45:01Z", "level": "INFO", "message": "Processing: F1 (2025) x265 EAC3 7.1 Bluray-1080p SAMPA.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": "Copied F1 (2025) x265 EAC3 7.1 Bluray-1080p SAMPA.mkv → F1 (2025) x265 EAC3 7.1 Bluray-1080p SAMPA.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "WARNING", "message": "Failed to calculate bitrate for stream 0: Command '['ffmpeg', '-y', '-i', 'processing\\\\F1 (2025) x265 EAC3 7.1 Bluray-1080p SAMPA.mkv', '-map', '0:a:0', '-c', 'copy', 'C:\\\\Users\\\\Tyler\\\\AppData\\\\Local\\\\Temp\\\\tmp7_i4l73e.aac']' returned non-zero exit status 4294967274.. Will fall back to metadata.", "module": "audio_handler", "funcName": "calculate_stream_bitrate", "line": 74}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": "Stream 1: Using fallback bitrate 1024 kbps", "module": "audio_handler", "funcName": "get_audio_streams", "line": 113}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": " - Stream #1: 8ch→6ch | Lang: und | Detected: 1024kbps | Action: ENCODE | Target: 448kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T19:46:09Z", "level": "INFO", "message": "Running CQ encode: F1 (2025) x265 EAC3 7.1 Bluray-1080p SAMPA -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T19:58:31Z", "level": "INFO", "message": "Removed from paths.txt: P:\\movies\\Ferris Bueller's Day Off (1986)", "module": "gui_path_manager", "funcName": "_remove_from_paths_file", "line": 703}
|
||||||
|
{"timestamp": "2025-12-31T19:58:32Z", "level": "INFO", "message": "Removed from paths.txt: P:\\movies\\The Lord of the Rings - The Two Towers - Extended Edition (2002)", "module": "gui_path_manager", "funcName": "_remove_from_paths_file", "line": 703}
|
||||||
|
{"timestamp": "2025-12-31T20:04:57Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T20:04:57Z", "level": "INFO", "message": " Original Size: 7923.16 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T20:04:57Z", "level": "INFO", "message": " Encoded Size: 4044.90 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T20:04:57Z", "level": "INFO", "message": " Reduction: 51.1% of original (48.9% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T20:04:57Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T20:04:57Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T20:05:19Z", "level": "INFO", "message": "Removed from paths.txt: P:\\movies\\John Wick - Chapter 3 - Parabellum (2019)", "module": "gui_path_manager", "funcName": "_remove_from_paths_file", "line": 703}
|
||||||
|
{"timestamp": "2025-12-31T20:05:27Z", "level": "INFO", "message": "Added to paths.txt: \"P:\\movies\\John Wick - Chapter 3 - Parabellum (2019)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 660}
|
||||||
|
{"timestamp": "2025-12-31T20:05:33Z", "level": "INFO", "message": "Removed from paths.txt: P:\\movies\\F1 (2025)", "module": "gui_path_manager", "funcName": "_remove_from_paths_file", "line": 703}
|
||||||
|
{"timestamp": "2025-12-31T20:05:35Z", "level": "INFO", "message": "Moved F1 (2025) x265 EAC3 7.1 Bluray-1080p SAMPA -EHX.mkv → F1 (2025) x265 EAC3 7.1 Bluray-1080p SAMPA -EHX.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 346}
|
||||||
|
{"timestamp": "2025-12-31T20:05:35Z", "level": "WARNING", "message": "Failed to calculate bitrate for stream 0: Command '['ffmpeg', '-y', '-i', 'processing\\\\F1 (2025) x265 EAC3 7.1 Bluray-1080p SAMPA.mkv', '-map', '0:a:0', '-c', 'copy', 'C:\\\\Users\\\\Tyler\\\\AppData\\\\Local\\\\Temp\\\\tmpnjrbzkz0.aac']' returned non-zero exit status 4294967274.. Will fall back to metadata.", "module": "audio_handler", "funcName": "calculate_stream_bitrate", "line": 74}
|
||||||
|
{"timestamp": "2025-12-31T20:05:35Z", "level": "INFO", "message": "Stream 1: Using fallback bitrate 1024 kbps", "module": "audio_handler", "funcName": "get_audio_streams", "line": 113}
|
||||||
|
{"timestamp": "2025-12-31T20:05:35Z", "level": "INFO", "message": "\n✅ CONVERSION COMPLETE: F1 (2025) x265 EAC3 7.1 Bluray-1080p SAMPA -EHX.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 386}
|
||||||
|
{"timestamp": "2025-12-31T20:05:35Z", "level": "INFO", "message": " Type: MOVIE | Show: N/A", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 387}
|
||||||
|
{"timestamp": "2025-12-31T20:05:35Z", "level": "INFO", "message": " Size: 7923.16MB → 4044.9MB (51.1% of original, 48.9% reduction)", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 388}
|
||||||
|
{"timestamp": "2025-12-31T20:05:35Z", "level": "INFO", "message": " Method: CQ | Status: SUCCESS", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 389}
|
||||||
|
{"timestamp": "2025-12-31T20:05:37Z", "level": "INFO", "message": "Deleted original and processing copy for F1 (2025) x265 EAC3 7.1 Bluray-1080p SAMPA.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 395}
|
||||||
|
{"timestamp": "2025-12-31T20:05:37Z", "level": "INFO", "message": "Batch processing complete", "module": "process_manager", "funcName": "process_folder", "line": 336}
|
||||||
|
{"timestamp": "2025-12-31T20:05:37Z", "level": "INFO", "message": "Using path as-is: P:\\movies\\John Wick - Chapter 3 - Parabellum (2019)", "module": "main", "funcName": "normalize_input_path", "line": 49}
|
||||||
|
{"timestamp": "2025-12-31T20:05:37Z", "level": "INFO", "message": "Processing: John Wick - Chapter 3 - Parabellum (2019) x265 AAC 7.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T20:05:37Z", "level": "INFO", "message": "Removed from paths.txt: P:\\movies\\The Lord of the Rings - The Return of the King - Extended Edition (2003)", "module": "gui_path_manager", "funcName": "_remove_from_paths_file", "line": 703}
|
||||||
|
{"timestamp": "2025-12-31T20:05:53Z", "level": "INFO", "message": "Removed from paths.txt: P:\\movies\\John Wick - Chapter 3 - Parabellum (2019)", "module": "gui_path_manager", "funcName": "_remove_from_paths_file", "line": 703}
|
||||||
|
{"timestamp": "2025-12-31T20:05:55Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\John Wick - Chapter 3 - Parabellum (2019)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 660}
|
||||||
|
{"timestamp": "2025-12-31T20:06:15Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\John Wick - Chapter 2 (2017)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 660}
|
||||||
|
{"timestamp": "2025-12-31T20:06:20Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\Belle (2021)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 660}
|
||||||
|
{"timestamp": "2025-12-31T20:06:22Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\TAYLOR SWIFT THE ERAS TOUR (2023)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 660}
|
||||||
|
{"timestamp": "2025-12-31T20:06:25Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\Ferris Bueller's Day Off (1986)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 660}
|
||||||
|
{"timestamp": "2025-12-31T20:06:40Z", "level": "INFO", "message": "Added to paths.txt: --m cq --r 720 \"P:\\movies\\The Baker (2023)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 660}
|
||||||
|
{"timestamp": "2025-12-31T20:07:10Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\The Losers (2010)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 660}
|
||||||
|
{"timestamp": "2025-12-31T20:07:17Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\Violent Night (2022)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 660}
|
||||||
|
{"timestamp": "2025-12-31T20:07:27Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\Scott Pilgrim vs. the World (2010)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 660}
|
||||||
|
{"timestamp": "2025-12-31T20:07:36Z", "level": "INFO", "message": "Added to paths.txt: --m cq \"P:\\movies\\Small Soldiers (1998)\"", "module": "gui_path_manager", "funcName": "_add_to_paths_file", "line": 660}
|
||||||
|
{"timestamp": "2025-12-31T20:07:44Z", "level": "INFO", "message": "Launched transcode.bat", "module": "gui_path_manager", "funcName": "_run_transcode", "line": 737}
|
||||||
|
{"timestamp": "2025-12-31T20:08:38Z", "level": "INFO", "message": "Using path as-is: P:\\movies\\John Wick - Chapter 4 (2023)", "module": "main", "funcName": "normalize_input_path", "line": 49}
|
||||||
|
{"timestamp": "2025-12-31T20:08:38Z", "level": "INFO", "message": "Skipping: John Wick - Chapter 4 (2023) x265 AAC 7.1 Bluray-1080p Tigole -EHX.mkv", "module": "process_manager", "funcName": "process_folder", "line": 97}
|
||||||
|
{"timestamp": "2025-12-31T20:08:38Z", "level": "INFO", "message": "Processing: A Shot in the Dark.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T20:08:39Z", "level": "INFO", "message": "Copied A Shot in the Dark.mkv → A Shot in the Dark.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T20:08:39Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:08:39Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T20:08:39Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T20:08:40Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T20:08:40Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T20:08:40Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:08:40Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T20:08:40Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T20:08:40Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T20:08:40Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T20:08:40Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T20:08:40Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T20:08:40Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:08:40Z", "level": "INFO", "message": "Running CQ encode: A Shot in the Dark -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T20:09:01Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T20:09:01Z", "level": "INFO", "message": " Original Size: 120.37 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T20:09:01Z", "level": "INFO", "message": " Encoded Size: 132.51 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T20:09:01Z", "level": "INFO", "message": " Reduction: 110.1% of original (-10.1% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T20:09:01Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T20:09:01Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T20:09:01Z", "level": "INFO", "message": "Processing: Chad and Keanu - Through Wick and Thin.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": "Copied Chad and Keanu - Through Wick and Thin.mkv → Chad and Keanu - Through Wick and Thin.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:09:02Z", "level": "INFO", "message": "Running CQ encode: Chad and Keanu - Through Wick and Thin -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T20:09:19Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T20:09:19Z", "level": "INFO", "message": " Original Size: 94.32 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T20:09:19Z", "level": "INFO", "message": " Encoded Size: 113.04 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T20:09:19Z", "level": "INFO", "message": " Reduction: 119.8% of original (-19.8% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T20:09:19Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T20:09:19Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T20:09:19Z", "level": "INFO", "message": "Processing: In Honor of the Dead.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": "Copied In Honor of the Dead.mkv → In Honor of the Dead.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": "Source resolution detected: 1920x1080", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": "Source 1920x1080 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": " • Source Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": " • Target Resolution: 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:09:20Z", "level": "INFO", "message": "Running CQ encode: In Honor of the Dead -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T20:09:41Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T20:09:41Z", "level": "INFO", "message": " Original Size: 88.34 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T20:09:41Z", "level": "INFO", "message": " Encoded Size: 109.24 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T20:09:41Z", "level": "INFO", "message": " Reduction: 123.7% of original (-23.7% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T20:09:41Z", "level": "INFO", "message": " Resolution: 1920x1080 → 1920x1080", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T20:09:41Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T20:09:41Z", "level": "ERROR", "message": "3 consecutive failures. Stopping process.", "module": "process_manager", "funcName": "process_folder", "line": 189}
|
||||||
|
{"timestamp": "2025-12-31T20:09:41Z", "level": "INFO", "message": "Batch processing complete", "module": "process_manager", "funcName": "process_folder", "line": 336}
|
||||||
|
{"timestamp": "2025-12-31T20:09:41Z", "level": "INFO", "message": "Using path as-is: P:\\movies\\Starship Troopers (1997)", "module": "main", "funcName": "normalize_input_path", "line": 49}
|
||||||
|
{"timestamp": "2025-12-31T20:09:41Z", "level": "INFO", "message": "Processing: Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T20:10:38Z", "level": "INFO", "message": "Copied Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigole.mkv → Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T20:10:38Z", "level": "INFO", "message": "Source resolution detected: 1920x1040", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:10:38Z", "level": "INFO", "message": "Source 1920x1040 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T20:10:38Z", "level": "INFO", "message": "Source 1920x1040 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T20:10:52Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T20:10:52Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T20:10:52Z", "level": "INFO", "message": " • Source Resolution: 1920x1040", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:10:52Z", "level": "INFO", "message": " • Target Resolution: 1920x1040", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T20:10:52Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T20:10:52Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T20:10:52Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T20:10:52Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T20:10:52Z", "level": "INFO", "message": " Audio Streams (4 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T20:10:52Z", "level": "INFO", "message": " - Stream #1: 6ch→6ch | Lang: und | Detected: 485kbps | Action: ENCODE | Target: 448kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:10:52Z", "level": "INFO", "message": " - Stream #2: 2ch→2ch | Lang: und | Detected: 67kbps | Action: COPY (preserve) | Target: 67kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:10:52Z", "level": "INFO", "message": " - Stream #3: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:10:52Z", "level": "INFO", "message": " - Stream #4: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:10:52Z", "level": "INFO", "message": "Running CQ encode: Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigole -EHX.mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T20:14:37Z", "level": "INFO", "message": "Using path as-is: P:\\movies\\Starship Troopers (1997)", "module": "main", "funcName": "normalize_input_path", "line": 49}
|
||||||
|
{"timestamp": "2025-12-31T20:14:37Z", "level": "INFO", "message": "Processing: Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T20:15:32Z", "level": "INFO", "message": "Copied Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigole.mkv → Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T20:15:33Z", "level": "INFO", "message": "Source resolution detected: 1920x1040", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:15:33Z", "level": "INFO", "message": "Source 1920x1040 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T20:15:33Z", "level": "INFO", "message": "Source 1920x1040 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T20:15:46Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T20:15:46Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T20:15:46Z", "level": "INFO", "message": " • Source Resolution: 1920x1040", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:15:46Z", "level": "INFO", "message": " • Target Resolution: 1920x1040", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T20:15:46Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T20:15:46Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T20:15:46Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T20:15:46Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T20:15:46Z", "level": "INFO", "message": " Audio Streams (4 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T20:15:46Z", "level": "INFO", "message": " - Stream #1: 6ch→6ch | Lang: und | Detected: 486kbps | Action: ENCODE | Target: 448kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:15:46Z", "level": "INFO", "message": " - Stream #2: 2ch→2ch | Lang: und | Detected: 66kbps | Action: COPY (preserve) | Target: 66kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:15:46Z", "level": "INFO", "message": " - Stream #3: 2ch→2ch | Lang: und | Detected: 67kbps | Action: COPY (preserve) | Target: 67kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:15:46Z", "level": "INFO", "message": " - Stream #4: 2ch→2ch | Lang: und | Detected: 67kbps | Action: COPY (preserve) | Target: 67kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:15:46Z", "level": "INFO", "message": "Running CQ encode: Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigole - [EHX].mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T20:29:30Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T20:29:30Z", "level": "INFO", "message": " Original Size: 6522.97 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T20:29:30Z", "level": "INFO", "message": " Encoded Size: 4495.60 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T20:29:30Z", "level": "INFO", "message": " Reduction: 68.9% of original (31.1% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T20:29:30Z", "level": "INFO", "message": " Resolution: 1920x1040 → 1920x1040", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T20:29:30Z", "level": "INFO", "message": " Audio Streams: 4 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T20:30:11Z", "level": "INFO", "message": "Moved Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigole - [EHX].mkv → Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigole - [EHX].mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 346}
|
||||||
|
{"timestamp": "2025-12-31T20:30:27Z", "level": "INFO", "message": "\n✅ CONVERSION COMPLETE: Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigole - [EHX].mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 386}
|
||||||
|
{"timestamp": "2025-12-31T20:30:27Z", "level": "INFO", "message": " Type: MOVIE | Show: N/A", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 387}
|
||||||
|
{"timestamp": "2025-12-31T20:30:27Z", "level": "INFO", "message": " Size: 6522.97MB → 4495.6MB (68.9% of original, 31.1% reduction)", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 388}
|
||||||
|
{"timestamp": "2025-12-31T20:30:27Z", "level": "INFO", "message": " Method: CQ | Status: SUCCESS", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 389}
|
||||||
|
{"timestamp": "2025-12-31T20:30:28Z", "level": "INFO", "message": "Deleted original and processing copy for Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "_save_successful_encoding", "line": 395}
|
||||||
|
{"timestamp": "2025-12-31T20:30:28Z", "level": "INFO", "message": "Processing: Death From Above.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T20:30:30Z", "level": "INFO", "message": "Copied Death From Above.mkv → Death From Above.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T20:30:30Z", "level": "INFO", "message": "Source resolution detected: 708x480", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:30:30Z", "level": "INFO", "message": "Source 708x480 (<=720p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 65}
|
||||||
|
{"timestamp": "2025-12-31T20:30:30Z", "level": "INFO", "message": "Source 708x480 (<=720p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 124}
|
||||||
|
{"timestamp": "2025-12-31T20:30:31Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T20:30:31Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T20:30:31Z", "level": "INFO", "message": " • Source Resolution: 708x480", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:30:31Z", "level": "INFO", "message": " • Target Resolution: 708x480", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T20:30:31Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T20:30:31Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T20:30:31Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T20:30:31Z", "level": "INFO", "message": " • CQ Value: 34", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T20:30:31Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T20:30:31Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 67kbps | Action: COPY (preserve) | Target: 67kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:30:31Z", "level": "INFO", "message": "Running CQ encode: Death From Above - [EHX].mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T20:31:08Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T20:31:08Z", "level": "INFO", "message": " Original Size: 209.73 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T20:31:08Z", "level": "INFO", "message": " Encoded Size: 189.88 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T20:31:08Z", "level": "INFO", "message": " Reduction: 90.5% of original (9.5% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T20:31:08Z", "level": "INFO", "message": " Resolution: 708x480 → 708x480", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T20:31:08Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T20:31:08Z", "level": "INFO", "message": "Processing: Deleted Scenes.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T20:31:08Z", "level": "INFO", "message": "Copied Deleted Scenes.mkv → Deleted Scenes.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T20:31:09Z", "level": "INFO", "message": "Source resolution detected: 710x346", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:31:09Z", "level": "INFO", "message": "Source 710x346 (<=720p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 65}
|
||||||
|
{"timestamp": "2025-12-31T20:31:09Z", "level": "INFO", "message": "Source 710x346 (<=720p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 124}
|
||||||
|
{"timestamp": "2025-12-31T20:31:09Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T20:31:09Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T20:31:09Z", "level": "INFO", "message": " • Source Resolution: 710x346", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:31:09Z", "level": "INFO", "message": " • Target Resolution: 710x346", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T20:31:09Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T20:31:09Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T20:31:09Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T20:31:09Z", "level": "INFO", "message": " • CQ Value: 34", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T20:31:09Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T20:31:09Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 67kbps | Action: COPY (preserve) | Target: 67kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:31:09Z", "level": "INFO", "message": "Running CQ encode: Deleted Scenes - [EHX].mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T20:31:19Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T20:31:19Z", "level": "INFO", "message": " Original Size: 49.76 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T20:31:19Z", "level": "INFO", "message": " Encoded Size: 37.43 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T20:31:19Z", "level": "INFO", "message": " Reduction: 75.2% of original (24.8% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T20:31:19Z", "level": "INFO", "message": " Resolution: 710x346 → 710x346", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T20:31:19Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T20:31:19Z", "level": "INFO", "message": "Processing: FX Comparisons.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T20:31:21Z", "level": "INFO", "message": "Copied FX Comparisons.mkv → FX Comparisons.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T20:31:21Z", "level": "INFO", "message": "Source resolution detected: 708x476", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:31:21Z", "level": "INFO", "message": "Source 708x476 (<=720p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 65}
|
||||||
|
{"timestamp": "2025-12-31T20:31:21Z", "level": "INFO", "message": "Source 708x476 (<=720p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 124}
|
||||||
|
{"timestamp": "2025-12-31T20:31:22Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T20:31:22Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T20:31:22Z", "level": "INFO", "message": " • Source Resolution: 708x476", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:31:22Z", "level": "INFO", "message": " • Target Resolution: 708x476", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T20:31:22Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T20:31:22Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T20:31:22Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T20:31:22Z", "level": "INFO", "message": " • CQ Value: 34", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T20:31:22Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T20:31:22Z", "level": "INFO", "message": " - Stream #1: 2ch→2ch | Lang: und | Detected: 64kbps | Action: COPY (preserve) | Target: 64kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:31:22Z", "level": "INFO", "message": "Running CQ encode: FX Comparisons - [EHX].mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
{"timestamp": "2025-12-31T20:31:57Z", "level": "INFO", "message": "\n📊 ENCODE RESULTS:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 107}
|
||||||
|
{"timestamp": "2025-12-31T20:31:57Z", "level": "INFO", "message": " Original Size: 247.74 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 108}
|
||||||
|
{"timestamp": "2025-12-31T20:31:57Z", "level": "INFO", "message": " Encoded Size: 214.33 MB", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 109}
|
||||||
|
{"timestamp": "2025-12-31T20:31:57Z", "level": "INFO", "message": " Reduction: 86.5% of original (13.5% saved)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 110}
|
||||||
|
{"timestamp": "2025-12-31T20:31:57Z", "level": "INFO", "message": " Resolution: 708x476 → 708x476", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 111}
|
||||||
|
{"timestamp": "2025-12-31T20:31:57Z", "level": "INFO", "message": " Audio Streams: 1 streams processed", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 112}
|
||||||
|
{"timestamp": "2025-12-31T20:31:57Z", "level": "ERROR", "message": "3 consecutive failures. Stopping process.", "module": "process_manager", "funcName": "process_folder", "line": 189}
|
||||||
|
{"timestamp": "2025-12-31T20:31:57Z", "level": "INFO", "message": "Batch processing complete", "module": "process_manager", "funcName": "process_folder", "line": 336}
|
||||||
|
{"timestamp": "2025-12-31T20:31:57Z", "level": "INFO", "message": "Using path as-is: P:\\movies\\John Wick - Chapter 3 - Parabellum (2019)", "module": "main", "funcName": "normalize_input_path", "line": 49}
|
||||||
|
{"timestamp": "2025-12-31T20:31:57Z", "level": "INFO", "message": "Processing: John Wick - Chapter 3 - Parabellum (2019) x265 AAC 7.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 101}
|
||||||
|
{"timestamp": "2025-12-31T20:32:54Z", "level": "INFO", "message": "Copied John Wick - Chapter 3 - Parabellum (2019) x265 AAC 7.1 Bluray-1080p Tigole.mkv → John Wick - Chapter 3 - Parabellum (2019) x265 AAC 7.1 Bluray-1080p Tigole.mkv", "module": "process_manager", "funcName": "process_folder", "line": 106}
|
||||||
|
{"timestamp": "2025-12-31T20:32:54Z", "level": "INFO", "message": "Source resolution detected: 1920x800", "module": "video_handler", "funcName": "get_source_resolution", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:32:54Z", "level": "INFO", "message": "Source 1920x800 (<=1080p). Preserving source resolution.", "module": "video_handler", "funcName": "determine_target_resolution", "line": 68}
|
||||||
|
{"timestamp": "2025-12-31T20:32:54Z", "level": "INFO", "message": "Source 1920x800 (<=1080p). Preserving source resolution.", "module": "process_manager", "funcName": "process_folder", "line": 127}
|
||||||
|
{"timestamp": "2025-12-31T20:33:04Z", "level": "INFO", "message": "\n🧩 ENCODE SETTINGS", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 25}
|
||||||
|
{"timestamp": "2025-12-31T20:33:04Z", "level": "INFO", "message": " Video:", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 28}
|
||||||
|
{"timestamp": "2025-12-31T20:33:04Z", "level": "INFO", "message": " • Source Resolution: 1920x800", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 29}
|
||||||
|
{"timestamp": "2025-12-31T20:33:04Z", "level": "INFO", "message": " • Target Resolution: 1920x800", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 30}
|
||||||
|
{"timestamp": "2025-12-31T20:33:04Z", "level": "INFO", "message": " • Encoder: av1_nvenc (preset p1, pix_fmt p010le)", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 31}
|
||||||
|
{"timestamp": "2025-12-31T20:33:04Z", "level": "INFO", "message": " • Scale Filter: lanczos", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 32}
|
||||||
|
{"timestamp": "2025-12-31T20:33:04Z", "level": "INFO", "message": " • Encode Method: CQ", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 33}
|
||||||
|
{"timestamp": "2025-12-31T20:33:04Z", "level": "INFO", "message": " • CQ Value: 32", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 35}
|
||||||
|
{"timestamp": "2025-12-31T20:33:04Z", "level": "INFO", "message": " Audio Streams (1 detected):", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 42}
|
||||||
|
{"timestamp": "2025-12-31T20:33:04Z", "level": "INFO", "message": " - Stream #1: 8ch→6ch | Lang: und | Detected: 696kbps | Action: ENCODE | Target: 448kbps", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 60}
|
||||||
|
{"timestamp": "2025-12-31T20:33:04Z", "level": "INFO", "message": "Running CQ encode: John Wick - Chapter 3 - Parabellum (2019) x265 AAC 7.1 Bluray-1080p Tigole - [EHX].mkv", "module": "encode_engine", "funcName": "run_ffmpeg", "line": 98}
|
||||||
|
|||||||
21
logs/failure.log
Normal file
21
logs/failure.log
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
2025-12-31 12:42:36 | Pacific Rim (2013) x265 AAC 7.1 Bluray-1080p Tigole.mkv | CQ failed: Size threshold not met (71.6%)
|
||||||
|
2025-12-31 12:42:54 | Behind the Scenes - Drift Space.mkv | CQ failed: Size threshold not met (122.5%)
|
||||||
|
2025-12-31 13:02:27 | Behind the Scenes - Drift Space.mkv | CQ failed: Size threshold not met (122.5%)
|
||||||
|
2025-12-31 13:03:26 | Behind the Scenes - The Digital Artistry of Pacific Rim.mkv | CQ failed: Size threshold not met (180.9%)
|
||||||
|
2025-12-31 13:04:07 | Behind the Scenes - The Shatterdome.mkv | CQ failed: Size threshold not met (174.6%)
|
||||||
|
2025-12-31 13:40:45 | The Keyboard Cowboys - A Look Back at Hackers.mkv | CQ failed: Size threshold not met (135.1%)
|
||||||
|
2025-12-31 13:40:54 | Trailer.mkv | CQ failed: Size threshold not met (124.7%)
|
||||||
|
2025-12-31 14:02:32 | The Making of The Truman Show.mkv | CQ failed: Size threshold not met (117.3%)
|
||||||
|
2025-12-31 14:02:45 | The Visual Effects of The Truman Show.mkv | CQ failed: Size threshold not met (106.3%)
|
||||||
|
2025-12-31 14:02:51 | Product Placement.mkv | CQ failed: Size threshold not met (106.8%)
|
||||||
|
2025-12-31 14:24:35 | A Shot in the Dark.mkv | CQ failed: Size threshold not met (110.1%)
|
||||||
|
2025-12-31 14:24:52 | Chad and Keanu - Through Wick and Thin.mkv | CQ failed: Size threshold not met (119.8%)
|
||||||
|
2025-12-31 14:25:08 | In Honor of the Dead.mkv | CQ failed: Size threshold not met (123.7%)
|
||||||
|
2025-12-31 14:44:59 | The Lord of the Rings - The Return of the King (2003) x265 EAC3 5.1 Bluray-1080p GalaxyRG265.mkv | CQ failed: Size threshold not met (79.0%)
|
||||||
|
2025-12-31 15:09:01 | A Shot in the Dark.mkv | CQ failed: Size threshold not met (110.1%)
|
||||||
|
2025-12-31 15:09:19 | Chad and Keanu - Through Wick and Thin.mkv | CQ failed: Size threshold not met (119.8%)
|
||||||
|
2025-12-31 15:09:41 | In Honor of the Dead.mkv | CQ failed: Size threshold not met (123.7%)
|
||||||
|
2025-12-31 15:13:45 | Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigole.mkv | CQ error: Command '['ffmpeg', '-y', '-i', 'processing\\Starship Troopers (1997) x265 AAC 5.1 Bluray-1080p Tigo
|
||||||
|
2025-12-31 15:31:08 | Death From Above.mkv | CQ failed: Size threshold not met (90.5%)
|
||||||
|
2025-12-31 15:31:19 | Deleted Scenes.mkv | CQ failed: Size threshold not met (75.2%)
|
||||||
|
2025-12-31 15:31:57 | FX Comparisons.mkv | CQ failed: Size threshold not met (86.5%)
|
||||||
571
main.py
571
main.py
@ -1,523 +1,116 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
AV1 Batch Video Transcoder
|
||||||
|
Main entry point for batch video encoding with intelligent audio and resolution handling.
|
||||||
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import csv
|
import csv
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from core.config_helper import load_config_xml
|
from core.config_helper import load_config_xml
|
||||||
from core.logger_helper import setup_logger
|
from core.logger_helper import setup_logger
|
||||||
|
from core.process_manager import process_folder
|
||||||
|
|
||||||
# =============================
|
# =============================
|
||||||
# Setup logger
|
# PATH NORMALIZATION
|
||||||
|
# =============================
|
||||||
|
def normalize_input_path(input_path: str, path_mappings: dict) -> Path:
|
||||||
|
"""
|
||||||
|
Normalize input path from various formats to Windows path.
|
||||||
|
|
||||||
|
Supports:
|
||||||
|
- Windows paths: "P:\\tv\\show" or "P:/tv/show"
|
||||||
|
- Linux paths: "/mnt/plex/tv/show" (maps to Windows equivalent if mapping exists)
|
||||||
|
- Mixed separators: "P:/tv\\show"
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_path: Path string from user input
|
||||||
|
path_mappings: Dict mapping Windows paths to Linux paths from config
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path object pointing to the actual local folder
|
||||||
|
"""
|
||||||
|
# First, try to map Linux paths to Windows paths (reverse mapping)
|
||||||
|
# If user provides "/mnt/plex/tv", find the mapping and convert to "P:\\tv"
|
||||||
|
for win_path, linux_path in path_mappings.items():
|
||||||
|
if input_path.lower().startswith(linux_path.lower()):
|
||||||
|
# Found a matching Linux path, convert to Windows
|
||||||
|
relative = input_path[len(linux_path):].lstrip("/").lstrip("\\")
|
||||||
|
result = Path(win_path) / relative if relative else Path(win_path)
|
||||||
|
logger.info(f"Path mapping: {input_path} -> {result}")
|
||||||
|
print(f"ℹ️ Mapped Linux path {input_path} to {result}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
# No mapping found, use path as-is (normalize separators to Windows)
|
||||||
|
# Convert forward slashes to backslashes for Windows
|
||||||
|
normalized = input_path.replace("/", "\\")
|
||||||
|
result = Path(normalized)
|
||||||
|
logger.info(f"Using path as-is: {result}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# Setup
|
||||||
# =============================
|
# =============================
|
||||||
LOG_FOLDER = Path(__file__).parent / "logs"
|
LOG_FOLDER = Path(__file__).parent / "logs"
|
||||||
logger = setup_logger(LOG_FOLDER)
|
logger = setup_logger(LOG_FOLDER)
|
||||||
|
|
||||||
# =============================
|
|
||||||
# Tracker CSV
|
|
||||||
# =============================
|
|
||||||
TRACKER_FILE = Path(__file__).parent / "conversion_tracker.csv"
|
TRACKER_FILE = Path(__file__).parent / "conversion_tracker.csv"
|
||||||
if not TRACKER_FILE.exists():
|
if not TRACKER_FILE.exists():
|
||||||
with open(TRACKER_FILE, "w", newline="", encoding="utf-8") as f:
|
with open(TRACKER_FILE, "w", newline="", encoding="utf-8") as f:
|
||||||
writer = csv.writer(f)
|
writer = csv.writer(f)
|
||||||
writer.writerow([
|
writer.writerow([
|
||||||
"type","show","filename","original_size_MB","processed_size_MB","percentage","method"
|
"type", "show", "filename", "original_size_MB", "processed_size_MB", "percentage",
|
||||||
|
"source_resolution", "target_resolution", "audio_streams", "cq_value", "method"
|
||||||
])
|
])
|
||||||
|
|
||||||
# =============================
|
|
||||||
# AUDIO BUCKET LOGIC
|
|
||||||
# =============================
|
|
||||||
def calculate_stream_bitrate(input_file: Path, stream_index: int) -> int:
|
|
||||||
"""
|
|
||||||
Extract audio stream to temporary file using -c copy, capture bitrate from ffmpeg output.
|
|
||||||
Returns bitrate in kbps. Falls back to 0 (and uses metadata) if extraction fails.
|
|
||||||
|
|
||||||
Uses ffmpeg's reported bitrate which is more accurate than calculating from file size/duration.
|
|
||||||
"""
|
|
||||||
temp_fd, temp_audio_path = tempfile.mkstemp(suffix=".aac", dir=None)
|
|
||||||
os.close(temp_fd)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Step 1: Extract audio stream with -c copy (lossless extraction)
|
|
||||||
# ffmpeg outputs bitrate info to stderr
|
|
||||||
extract_cmd = [
|
|
||||||
"ffmpeg", "-y", "-i", str(input_file),
|
|
||||||
"-map", f"0:a:{stream_index}",
|
|
||||||
"-c", "copy",
|
|
||||||
temp_audio_path
|
|
||||||
]
|
|
||||||
logger.debug(f"Extracting audio stream {stream_index} to temporary file for bitrate calculation...")
|
|
||||||
result = subprocess.run(extract_cmd, capture_output=True, text=True, check=True)
|
|
||||||
|
|
||||||
# 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"):
|
|
||||||
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=")
|
|
||||||
if len(parts) > 1:
|
|
||||||
bitrate_str = parts[1].strip().split("kbits/s")[0].strip()
|
|
||||||
try:
|
|
||||||
bitrate_kbps = int(float(bitrate_str))
|
|
||||||
logger.debug(f"Stream {stream_index}: Extracted bitrate from ffmpeg output: {bitrate_kbps} kbps")
|
|
||||||
break
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# If we couldn't parse bitrate from output, fall back to calculation
|
|
||||||
if bitrate_kbps == 0:
|
|
||||||
logger.debug(f"Stream {stream_index}: Could not parse bitrate from ffmpeg output, calculating from file size...")
|
|
||||||
file_size_bytes = os.path.getsize(temp_audio_path)
|
|
||||||
|
|
||||||
# Get duration using ffprobe
|
|
||||||
duration_cmd = [
|
|
||||||
"ffprobe", "-v", "error",
|
|
||||||
"-show_entries", "format=duration",
|
|
||||||
"-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")
|
|
||||||
|
|
||||||
return bitrate_kbps
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Failed to calculate bitrate for stream {stream_index}: {e}. Will fall back to metadata.")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
finally:
|
|
||||||
# Clean up temporary audio file
|
|
||||||
try:
|
|
||||||
if os.path.exists(temp_audio_path):
|
|
||||||
os.remove(temp_audio_path)
|
|
||||||
logger.debug(f"Deleted temporary audio file: {temp_audio_path}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Could not delete temporary file {temp_audio_path}: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def choose_audio_bitrate(channels: int, bitrate_kbps: int, audio_config: dict, is_1080_class: bool) -> tuple:
|
|
||||||
"""
|
|
||||||
Choose audio codec and bitrate based on channel count, detected bitrate, and resolution.
|
|
||||||
|
|
||||||
Returns tuple: (codec, target_bitrate_bps)
|
|
||||||
- codec: "aac", "libopus", or "copy" (to preserve original without re-encoding)
|
|
||||||
- target_bitrate_bps: target bitrate in bits/sec (0 if using "copy")
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
Stereo + 1080p:
|
|
||||||
- Above 192k → high (192k) with AAC
|
|
||||||
- At/below 192k → preserve (copy)
|
|
||||||
|
|
||||||
Stereo + 720p:
|
|
||||||
- Above 160k → medium (160k) with AAC
|
|
||||||
- At/below 160k → preserve (copy)
|
|
||||||
|
|
||||||
Multi-channel:
|
|
||||||
- Below 384k → low (384k) with AAC
|
|
||||||
- 384k to below medium → low (384k) with AAC
|
|
||||||
- Medium and above → medium with AAC
|
|
||||||
"""
|
|
||||||
# Normalize to 2ch or 6ch output
|
|
||||||
output_channels = 6 if channels >= 6 else 2
|
|
||||||
|
|
||||||
if output_channels == 2:
|
|
||||||
# Stereo logic
|
|
||||||
if is_1080_class:
|
|
||||||
# 1080p+ stereo
|
|
||||||
high_br = audio_config["stereo"]["high"]
|
|
||||||
if bitrate_kbps > (high_br / 1000): # Above 192k
|
|
||||||
return ("aac", high_br)
|
|
||||||
else:
|
|
||||||
# Preserve original
|
|
||||||
return ("copy", 0)
|
|
||||||
else:
|
|
||||||
# 720p stereo
|
|
||||||
medium_br = audio_config["stereo"]["medium"]
|
|
||||||
if bitrate_kbps > (medium_br / 1000): # Above 160k
|
|
||||||
return ("aac", medium_br)
|
|
||||||
else:
|
|
||||||
# Preserve original
|
|
||||||
return ("copy", 0)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Multi-channel (6ch+) logic
|
|
||||||
low_br = audio_config["multi_channel"]["low"]
|
|
||||||
medium_br = audio_config["multi_channel"]["medium"]
|
|
||||||
|
|
||||||
if bitrate_kbps < (medium_br / 1000):
|
|
||||||
# Below medium, use low
|
|
||||||
return ("aac", low_br)
|
|
||||||
else:
|
|
||||||
# Medium and above, use medium
|
|
||||||
return ("aac", medium_br)
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# PATH NORMALIZATION
|
|
||||||
# =============================
|
|
||||||
def normalize_path_for_service(local_path: str, path_mappings: dict) -> str:
|
|
||||||
for win_path, linux_path in path_mappings.items():
|
|
||||||
if local_path.lower().startswith(win_path.lower()):
|
|
||||||
return local_path.replace(win_path, linux_path).replace("\\", "/")
|
|
||||||
return local_path.replace("\\", "/")
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# AUDIO STREAMS DETECTION
|
|
||||||
# =============================
|
|
||||||
def get_source_resolution(input_file: Path) -> tuple:
|
|
||||||
"""
|
|
||||||
Get source video resolution (width, height).
|
|
||||||
Returns tuple: (width, height)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
cmd = [
|
|
||||||
"ffprobe", "-v", "error",
|
|
||||||
"-select_streams", "v:0",
|
|
||||||
"-show_entries", "stream=width,height",
|
|
||||||
"-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)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Failed to detect source resolution: {e}. Defaulting to 1920x1080")
|
|
||||||
return (1920, 1080)
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
"""
|
|
||||||
cmd = [
|
|
||||||
"ffprobe","-v","error","-select_streams","a",
|
|
||||||
"-show_entries","stream=index,channels,bit_rate,tags=language",
|
|
||||||
"-of","json", str(input_file)
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
data = json.loads(result.stdout)
|
|
||||||
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")
|
|
||||||
bit_rate_meta = int(s.get("bit_rate", 0)) if s.get("bit_rate") else 0
|
|
||||||
|
|
||||||
# Calculate robust bitrate by extracting the audio stream
|
|
||||||
calculated_bitrate_kbps = calculate_stream_bitrate(input_file, stream_num)
|
|
||||||
|
|
||||||
# If calculation failed, fall back to metadata
|
|
||||||
if calculated_bitrate_kbps == 0:
|
|
||||||
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))
|
|
||||||
|
|
||||||
return streams
|
|
||||||
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# FFmpeg ENCODE
|
|
||||||
# =============================
|
|
||||||
def run_ffmpeg(input_file: Path, output_file: Path, cq: int, scale_width: int, scale_height: int,
|
|
||||||
filter_flags: str, audio_config: dict, method: str, bitrate_config: dict):
|
|
||||||
# Get source resolution
|
|
||||||
src_width, src_height = get_source_resolution(input_file)
|
|
||||||
|
|
||||||
streams = get_audio_streams(input_file)
|
|
||||||
|
|
||||||
# Log comprehensive encode settings
|
|
||||||
header = f"\n🧩 ENCODE SETTINGS"
|
|
||||||
logger.info(header)
|
|
||||||
print(" ")
|
|
||||||
|
|
||||||
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" • Scale Filter: {filter_flags}")
|
|
||||||
logger.info(f" • Encode Method: {method}")
|
|
||||||
if method == "CQ":
|
|
||||||
logger.info(f" • CQ Value: {cq}")
|
|
||||||
else:
|
|
||||||
res_key = "1080" if scale_height >= 1080 or scale_width >= 1920 else "720"
|
|
||||||
vb = bitrate_config.get(f"bitrate_{res_key}", "900k")
|
|
||||||
maxrate = bitrate_config.get(f"maxrate_{res_key}", "1250k")
|
|
||||||
logger.info(f" • Bitrate: {vb}, Max: {maxrate}")
|
|
||||||
|
|
||||||
logger.info(f" Audio Streams ({len(streams)} detected):")
|
|
||||||
print(" ")
|
|
||||||
|
|
||||||
for (index, channels, avg_bitrate, src_lang, meta_bitrate) 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
|
|
||||||
codec, br = choose_audio_bitrate(output_channels, avg_bitrate, audio_config, is_1080_class)
|
|
||||||
|
|
||||||
if codec == "copy":
|
|
||||||
action = "COPY (preserve)"
|
|
||||||
bitrate_display = f"{avg_bitrate}kbps"
|
|
||||||
else:
|
|
||||||
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}"
|
|
||||||
print(line)
|
|
||||||
logger.info(line)
|
|
||||||
|
|
||||||
cmd = ["ffmpeg","-y","-i",str(input_file),
|
|
||||||
"-vf",f"scale={scale_width}:{scale_height}:flags={filter_flags}:force_original_aspect_ratio=decrease",
|
|
||||||
"-map","0:v","-map","0:a","-map","0:s?",
|
|
||||||
"-c:v","av1_nvenc","-preset","p1","-pix_fmt","p010le"]
|
|
||||||
|
|
||||||
if method=="CQ":
|
|
||||||
cmd += ["-cq", str(cq)]
|
|
||||||
else:
|
|
||||||
# Use bitrate config (fallback mode)
|
|
||||||
res_key = "1080" if scale_height >= 1080 or scale_width >= 1920 else "720"
|
|
||||||
vb = bitrate_config.get(f"bitrate_{res_key}", "900k")
|
|
||||||
maxrate = bitrate_config.get(f"maxrate_{res_key}", "1250k")
|
|
||||||
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):
|
|
||||||
# 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
|
|
||||||
codec, br = choose_audio_bitrate(output_channels, avg_bitrate, audio_config, is_1080_class)
|
|
||||||
|
|
||||||
if codec == "copy":
|
|
||||||
# Preserve original audio
|
|
||||||
cmd += [f"-c:a:{i}", "copy"]
|
|
||||||
else:
|
|
||||||
# Re-encode with target bitrate
|
|
||||||
cmd += [
|
|
||||||
f"-c:a:{i}", codec,
|
|
||||||
f"-b:a:{i}", str(br),
|
|
||||||
f"-ac:{i}", str(output_channels),
|
|
||||||
f"-channel_layout:a:{i}", "5.1" if output_channels == 6 else "stereo"
|
|
||||||
]
|
|
||||||
|
|
||||||
cmd += ["-c:s","copy",str(output_file)]
|
|
||||||
|
|
||||||
print(f"\n🎬 Running {method} encode: {output_file.name}")
|
|
||||||
logger.info(f"Running {method} encode: {output_file.name}")
|
|
||||||
|
|
||||||
subprocess.run(cmd, check=True)
|
|
||||||
|
|
||||||
orig_size = input_file.stat().st_size
|
|
||||||
out_size = output_file.stat().st_size
|
|
||||||
reduction_ratio = out_size / orig_size
|
|
||||||
|
|
||||||
# Log comprehensive results
|
|
||||||
logger.info(f"\n📊 ENCODE RESULTS:")
|
|
||||||
logger.info(f" Original Size: {orig_size/1e6:.2f} MB")
|
|
||||||
logger.info(f" Encoded Size: {out_size/1e6:.2f} MB")
|
|
||||||
logger.info(f" Reduction: {reduction_ratio:.1%} of original ({(1-reduction_ratio):.1%} saved)")
|
|
||||||
logger.info(f" Resolution: {src_width}x{src_height} → {scale_width}x{scale_height}")
|
|
||||||
logger.info(f" Audio Streams: {len(streams)} streams processed")
|
|
||||||
|
|
||||||
msg = f"📦 Original: {orig_size/1e6:.2f} MB → Encoded: {out_size/1e6:.2f} MB ({reduction_ratio:.1%} of original)"
|
|
||||||
print(msg)
|
|
||||||
|
|
||||||
return orig_size, out_size, reduction_ratio
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# PROCESS FOLDER
|
|
||||||
# =============================
|
|
||||||
def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str, config: dict):
|
|
||||||
if not folder.exists():
|
|
||||||
print(f"❌ Folder not found: {folder}")
|
|
||||||
logger.error(f"Folder not found: {folder}")
|
|
||||||
return
|
|
||||||
|
|
||||||
audio_config = config["audio"]
|
|
||||||
bitrate_config = config["encode"]["fallback"]
|
|
||||||
filters_config = config["encode"]["filters"]
|
|
||||||
suffix = config["suffix"]
|
|
||||||
extensions = config["extensions"]
|
|
||||||
ignore_tags = config["ignore_tags"]
|
|
||||||
reduction_ratio_threshold = config["reduction_ratio_threshold"]
|
|
||||||
|
|
||||||
# Resolution logic: explicit arg takes precedence, else use smart defaults
|
|
||||||
explicit_resolution = resolution # Will be None if not specified
|
|
||||||
|
|
||||||
filter_flags = filters_config.get("default","lanczos")
|
|
||||||
folder_lower = str(folder).lower()
|
|
||||||
is_tv = "\\tv\\" in folder_lower or "/tv/" in folder_lower
|
|
||||||
if is_tv:
|
|
||||||
filter_flags = filters_config.get("tv","bicubic")
|
|
||||||
|
|
||||||
processing_folder = Path(config["processing_folder"])
|
|
||||||
processing_folder.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
# Track if we switch to bitrate mode
|
|
||||||
use_bitrate = True if transcode_mode == "bitrate" else False
|
|
||||||
|
|
||||||
for file in folder.rglob("*"):
|
|
||||||
if file.suffix.lower() not in extensions:
|
|
||||||
continue
|
|
||||||
if any(tag.lower() in file.name.lower() for tag in ignore_tags):
|
|
||||||
print(f"⏭️ Skipping: {file.name}")
|
|
||||||
logger.info(f"Skipping: {file.name}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
print("="*60)
|
|
||||||
logger.info(f"Processing: {file.name}")
|
|
||||||
print(f"📁 Processing: {file.name}")
|
|
||||||
|
|
||||||
temp_input = processing_folder / file.name
|
|
||||||
shutil.copy2(file, temp_input)
|
|
||||||
logger.info(f"Copied {file.name} → {temp_input.name}")
|
|
||||||
|
|
||||||
# Detect source resolution and determine target resolution
|
|
||||||
src_width, src_height = get_source_resolution(temp_input)
|
|
||||||
|
|
||||||
# Smart resolution logic
|
|
||||||
if explicit_resolution:
|
|
||||||
# User explicitly specified resolution - always use it
|
|
||||||
target_resolution = explicit_resolution
|
|
||||||
if target_resolution == "1080":
|
|
||||||
res_height = 1080
|
|
||||||
res_width = 1920
|
|
||||||
elif target_resolution == "720":
|
|
||||||
res_height = 720
|
|
||||||
res_width = 1280
|
|
||||||
else: # 480
|
|
||||||
res_height = 480
|
|
||||||
res_width = 854
|
|
||||||
logger.info(f"Using explicitly specified resolution: {res_width}x{res_height}")
|
|
||||||
else:
|
|
||||||
# No explicit resolution - use smart defaults
|
|
||||||
if src_height > 1080:
|
|
||||||
# Scale down anything above 1080p to 1080p
|
|
||||||
target_resolution = "1080"
|
|
||||||
res_height = 1080
|
|
||||||
res_width = 1920
|
|
||||||
print(f"⚠️ Source {src_width}x{src_height} is above 1080p. Scaling down to 1080p.")
|
|
||||||
logger.info(f"Source {src_width}x{src_height} detected. Scaling to 1080p.")
|
|
||||||
else:
|
|
||||||
# Preserve source resolution (480p, 720p, 1080p, etc.)
|
|
||||||
res_height = src_height
|
|
||||||
res_width = src_width
|
|
||||||
if src_height <= 720:
|
|
||||||
target_resolution = "720"
|
|
||||||
print(f"ℹ️ Source {src_width}x{src_height} is 720p or lower. Preserving resolution.")
|
|
||||||
logger.info(f"Source {src_width}x{src_height} (<=720p). Preserving source resolution.")
|
|
||||||
else:
|
|
||||||
target_resolution = "1080"
|
|
||||||
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)
|
|
||||||
file_cq = cq if cq is not None else content_cq
|
|
||||||
|
|
||||||
temp_output = processing_folder / f"{file.stem}{suffix}{file.suffix}"
|
|
||||||
|
|
||||||
method = "Bitrate" if use_bitrate else "CQ"
|
|
||||||
try:
|
|
||||||
orig_size, out_size, reduction_ratio = run_ffmpeg(temp_input, temp_output, file_cq, res_width, res_height, filter_flags, audio_config, method, bitrate_config)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"❌ FFmpeg failed: {e}")
|
|
||||||
logger.error(f"FFmpeg failed: {e}")
|
|
||||||
temp_input.unlink(missing_ok=True)
|
|
||||||
break
|
|
||||||
|
|
||||||
if method=="CQ" and reduction_ratio>=reduction_ratio_threshold:
|
|
||||||
print(f"⚠️ CQ encode did not achieve target size ({reduction_ratio:.1%} >= {reduction_ratio_threshold:.1%}). Switching all remaining files to Bitrate.")
|
|
||||||
logger.warning(f"CQ encode failed target ({reduction_ratio:.1%}). Switching to Bitrate for remaining files.")
|
|
||||||
use_bitrate = True
|
|
||||||
try:
|
|
||||||
# Retry current file using bitrate
|
|
||||||
temp_output.unlink(missing_ok=True)
|
|
||||||
orig_size, out_size, reduction_ratio = run_ffmpeg(temp_input, temp_output, cq, res_width, res_height, filter_flags, audio_config, "Bitrate", bitrate_config)
|
|
||||||
if reduction_ratio>=reduction_ratio_threshold:
|
|
||||||
print(f"❌ Bitrate encode also failed target ({reduction_ratio:.1%}). Stopping process.")
|
|
||||||
logger.error(f"Bitrate encode failed target ({reduction_ratio:.1%}). Stopping process.")
|
|
||||||
temp_input.unlink(missing_ok=True)
|
|
||||||
break
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"❌ Bitrate retry failed: {e}")
|
|
||||||
logger.error(f"Bitrate retry failed: {e}")
|
|
||||||
temp_input.unlink(missing_ok=True)
|
|
||||||
break
|
|
||||||
elif method=="Bitrate" and reduction_ratio>=reduction_ratio_threshold:
|
|
||||||
print(f"❌ Bitrate encode failed target ({reduction_ratio:.1%}). Stopping process.")
|
|
||||||
logger.error(f"Bitrate encode failed target ({reduction_ratio:.1%}). Stopping process.")
|
|
||||||
temp_input.unlink(missing_ok=True)
|
|
||||||
break
|
|
||||||
|
|
||||||
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}")
|
|
||||||
|
|
||||||
folder_parts = [p.lower() for p in folder.parts]
|
|
||||||
if "tv" in folder_parts:
|
|
||||||
f_type = "tv"
|
|
||||||
tv_index = folder_parts.index("tv")
|
|
||||||
show = folder.parts[tv_index + 1] if len(folder.parts) > tv_index + 1 else "Unknown"
|
|
||||||
elif "anime" in folder_parts:
|
|
||||||
f_type = "anime"
|
|
||||||
anime_index = folder_parts.index("anime")
|
|
||||||
show = folder.parts[anime_index + 1] if len(folder.parts) > anime_index + 1 else "Unknown"
|
|
||||||
else:
|
|
||||||
f_type = "movie"
|
|
||||||
show = "N/A"
|
|
||||||
|
|
||||||
orig_size_mb = round(orig_size / 1e6, 2)
|
|
||||||
proc_size_mb = round(out_size / 1e6, 2)
|
|
||||||
percentage = round(proc_size_mb / orig_size_mb * 100, 1)
|
|
||||||
|
|
||||||
with open(TRACKER_FILE, "a", newline="", encoding="utf-8") as f:
|
|
||||||
writer = csv.writer(f)
|
|
||||||
writer.writerow([f_type, show, dest_file.name, orig_size_mb, proc_size_mb, percentage, method])
|
|
||||||
|
|
||||||
# Enhanced logging with all conversion details
|
|
||||||
logger.info(f"\n✅ CONVERSION COMPLETE: {dest_file.name}")
|
|
||||||
logger.info(f" Type: {f_type.upper()} | Show: {show}")
|
|
||||||
logger.info(f" Size: {orig_size_mb}MB → {proc_size_mb}MB ({percentage}% of original, {100-percentage:.1f}% reduction)")
|
|
||||||
logger.info(f" Method: {method} | Status: SUCCESS")
|
|
||||||
print(f"📝 Logged conversion: {dest_file.name} ({percentage}%), method={method}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
temp_input.unlink()
|
|
||||||
file.unlink()
|
|
||||||
logger.info(f"Deleted original and processing copy for {file.name}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"⚠️ Could not delete files: {e}")
|
|
||||||
logger.warning(f"Could not delete files: {e}")
|
|
||||||
|
|
||||||
# =============================
|
# =============================
|
||||||
# MAIN
|
# MAIN
|
||||||
# =============================
|
# =============================
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="Batch encode videos with logging and tracker")
|
parser = argparse.ArgumentParser(
|
||||||
parser.add_argument("folder", help="Path to folder containing videos")
|
description="Batch AV1 encode videos with intelligent audio and resolution handling",
|
||||||
parser.add_argument("--cq", type=int, help="Override default CQ")
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
parser.add_argument("--m", "--mode", dest="transcode_mode", default="cq", choices=["cq","bitrate"], help="Encode mode (cq or bitrate)")
|
epilog="""
|
||||||
parser.add_argument("--r", "--resolution", dest="resolution", default=None, choices=["480","720","1080"], help="Force target resolution (if not specified, preserves source if <=1080p, else scales to 1080p)")
|
Examples:
|
||||||
|
%(prog)s "C:\\Videos\\Movies" # Smart 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
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument("folder", help="Input folder containing video files")
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--r", "--resolution", dest="resolution", default=None,
|
||||||
|
choices=["480", "720", "1080"],
|
||||||
|
help="Force target resolution (if not specified: 4K->1080p, else preserve)"
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Load configuration
|
||||||
config_path = Path(__file__).parent / "config.xml"
|
config_path = Path(__file__).parent / "config.xml"
|
||||||
config = load_config_xml(config_path)
|
config = load_config_xml(config_path)
|
||||||
|
|
||||||
process_folder(Path(args.folder), args.cq, args.transcode_mode, args.resolution, config)
|
# Normalize input path (handle Linux paths, mixed separators, etc.)
|
||||||
|
folder = normalize_input_path(args.folder, config.get("path_mappings", {}))
|
||||||
|
|
||||||
|
# Verify folder exists
|
||||||
|
if not folder.exists():
|
||||||
|
print(f"❌ Folder not found: {folder}")
|
||||||
|
logger.error(f"Folder not found: {folder}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Process folder
|
||||||
|
process_folder(folder, args.cq, args.transcode_mode, args.resolution, config, TRACKER_FILE)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
14
paths.txt
14
paths.txt
@ -1,3 +1,11 @@
|
|||||||
--r 720 --m bitrate "P:\tv\Rupaul's Drag Race UK"
|
--m cq "P:\movies\Starship Troopers (1997)"
|
||||||
--r 720 --m bitrate "P:\tv\Canada's Drag Race"
|
--m cq "P:\movies\John Wick - Chapter 3 - Parabellum (2019)"
|
||||||
--r 720 --m bitrate "P:\tv\Canada's Drag Race vs The World"
|
--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)"
|
||||||
|
|||||||
@ -1,18 +1,47 @@
|
|||||||
@echo off
|
@echo off
|
||||||
|
REM ====================================================================
|
||||||
|
REM Batch Transcode Queue Runner
|
||||||
|
REM Reads paths.txt and processes each line as a separate encode job
|
||||||
|
REM Each line should be a Python command with arguments, e.g.:
|
||||||
|
REM --r 720 --m bitrate "C:\Videos\TV Show"
|
||||||
|
REM --r 1080 --cq 28 "C:\Videos\Movies"
|
||||||
|
REM ====================================================================
|
||||||
|
|
||||||
setlocal enabledelayedexpansion
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ====================================================================
|
||||||
|
echo Starting Batch Transcode Queue
|
||||||
|
echo ====================================================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
set "JOB_COUNT=0"
|
||||||
|
set "SUCCESS_COUNT=0"
|
||||||
|
set "FAILED_COUNT=0"
|
||||||
|
|
||||||
for /f "usebackq delims=" %%i in ("paths.txt") do (
|
for /f "usebackq delims=" %%i in ("paths.txt") do (
|
||||||
echo ==============================
|
set /a JOB_COUNT+=1
|
||||||
echo Processing: %%i
|
echo.
|
||||||
echo ==============================
|
echo [Job !JOB_COUNT!] Processing: %%i
|
||||||
|
echo ======================================
|
||||||
|
|
||||||
py main.py %%i
|
py main.py %%i
|
||||||
|
|
||||||
if errorlevel 1 (
|
if errorlevel 1 (
|
||||||
echo ERROR processing %%i
|
set /a FAILED_COUNT+=1
|
||||||
echo Continuing to next item...
|
echo [Job !JOB_COUNT!] FAILED - Continuing to next item...
|
||||||
|
) else (
|
||||||
|
set /a SUCCESS_COUNT+=1
|
||||||
|
echo [Job !JOB_COUNT!] SUCCESS
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo All jobs finished.
|
echo ====================================================================
|
||||||
|
echo Batch Transcode Queue Complete
|
||||||
|
echo ====================================================================
|
||||||
|
echo Total Jobs: !JOB_COUNT!
|
||||||
|
echo Successful: !SUCCESS_COUNT!
|
||||||
|
echo Failed: !FAILED_COUNT!
|
||||||
|
echo ====================================================================
|
||||||
pause
|
pause
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user