diff --git a/arduino/.gitignore b/arduino/.gitignore deleted file mode 100644 index 89cc49c..0000000 --- a/arduino/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.pio -.vscode/.browse.c_cpp.db* -.vscode/c_cpp_properties.json -.vscode/launch.json -.vscode/ipch diff --git a/arduino/.pio/build/project.checksum b/arduino/.pio/build/project.checksum new file mode 100644 index 0000000..920fa9b --- /dev/null +++ b/arduino/.pio/build/project.checksum @@ -0,0 +1 @@ +dbe76af323ba99fa5d31228003a40ed9c8f93bc9 \ No newline at end of file diff --git a/arduino/.pio/build/uno/.sconsign312.dblite b/arduino/.pio/build/uno/.sconsign312.dblite new file mode 100644 index 0000000..bd27106 Binary files /dev/null and b/arduino/.pio/build/uno/.sconsign312.dblite differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/CDC.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/CDC.cpp.o new file mode 100644 index 0000000..3a340a3 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/CDC.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial.cpp.o new file mode 100644 index 0000000..4b35c72 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial0.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial0.cpp.o new file mode 100644 index 0000000..a2427ad Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial0.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial1.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial1.cpp.o new file mode 100644 index 0000000..09e1df3 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial1.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial2.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial2.cpp.o new file mode 100644 index 0000000..500c201 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial2.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial3.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial3.cpp.o new file mode 100644 index 0000000..13f3d9f Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/HardwareSerial3.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/IPAddress.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/IPAddress.cpp.o new file mode 100644 index 0000000..e4f8edb Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/IPAddress.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/PluggableUSB.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/PluggableUSB.cpp.o new file mode 100644 index 0000000..e47c43d Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/PluggableUSB.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/Print.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/Print.cpp.o new file mode 100644 index 0000000..a2fc97e Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/Print.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/Stream.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/Stream.cpp.o new file mode 100644 index 0000000..aabf676 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/Stream.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/Tone.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/Tone.cpp.o new file mode 100644 index 0000000..260134f Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/Tone.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/USBCore.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/USBCore.cpp.o new file mode 100644 index 0000000..be52ff3 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/USBCore.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/WInterrupts.c.o b/arduino/.pio/build/uno/FrameworkArduino/WInterrupts.c.o new file mode 100644 index 0000000..c15fb66 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/WInterrupts.c.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/WMath.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/WMath.cpp.o new file mode 100644 index 0000000..5b2a28e Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/WMath.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/WString.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/WString.cpp.o new file mode 100644 index 0000000..6180563 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/WString.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/abi.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/abi.cpp.o new file mode 100644 index 0000000..4acbff7 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/abi.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/hooks.c.o b/arduino/.pio/build/uno/FrameworkArduino/hooks.c.o new file mode 100644 index 0000000..12437f5 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/hooks.c.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/main.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/main.cpp.o new file mode 100644 index 0000000..4af2776 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/main.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/new.cpp.o b/arduino/.pio/build/uno/FrameworkArduino/new.cpp.o new file mode 100644 index 0000000..965df84 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/new.cpp.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/wiring.c.o b/arduino/.pio/build/uno/FrameworkArduino/wiring.c.o new file mode 100644 index 0000000..3b9528f Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/wiring.c.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/wiring_analog.c.o b/arduino/.pio/build/uno/FrameworkArduino/wiring_analog.c.o new file mode 100644 index 0000000..6160399 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/wiring_analog.c.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/wiring_digital.c.o b/arduino/.pio/build/uno/FrameworkArduino/wiring_digital.c.o new file mode 100644 index 0000000..f46cd0f Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/wiring_digital.c.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/wiring_pulse.S.o b/arduino/.pio/build/uno/FrameworkArduino/wiring_pulse.S.o new file mode 100644 index 0000000..167ab7f Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/wiring_pulse.S.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/wiring_pulse.c.o b/arduino/.pio/build/uno/FrameworkArduino/wiring_pulse.c.o new file mode 100644 index 0000000..e21c787 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/wiring_pulse.c.o differ diff --git a/arduino/.pio/build/uno/FrameworkArduino/wiring_shift.c.o b/arduino/.pio/build/uno/FrameworkArduino/wiring_shift.c.o new file mode 100644 index 0000000..c00e1d3 Binary files /dev/null and b/arduino/.pio/build/uno/FrameworkArduino/wiring_shift.c.o differ diff --git a/arduino/.pio/build/uno/firmware.elf b/arduino/.pio/build/uno/firmware.elf new file mode 100755 index 0000000..606683e Binary files /dev/null and b/arduino/.pio/build/uno/firmware.elf differ diff --git a/arduino/.pio/build/uno/firmware.hex b/arduino/.pio/build/uno/firmware.hex new file mode 100644 index 0000000..675a15a --- /dev/null +++ b/arduino/.pio/build/uno/firmware.hex @@ -0,0 +1,279 @@ +:100000000C94D4000C94FC000C94FC000C94FC00A8 +:100010000C94FC000C94FC000C94FC000C94FC0070 +:100020000C94FC000C94FC000C9435040C94A40477 +:100030000C94FC000C94FC000C94FC000C94FC0050 +:100040000C94FC000C94FC000C94FC000C94FC0040 +:100050000C94FC000C94FC000C94FC000C94EB033E +:100060000C94FC000C94FC000C94FC000C94FC0020 +:100070000C94FC000C94FC000C94FC000C94FC0010 +:100080000C94FC000C94FC000C94FC000C94FC0000 +:100090000C94FC000C94FC000C94FC000C94FC00F0 +:1000A0000C94FC000C94FC000C94FC00080B000267 +:1000B0000202000009040000010202000005240001 +:1000C0001001052401010104240206052406000193 +:1000D0000705810310004009040100020A00000026 +:1000E00007050202400000070583024000000403E8 +:1000F000090412010002EF02014041233780000190 +:100100000102030141726475696E6F204C4C43001B +:1001100041726475696E6F204D6963726F000000F3 +:100120000000250028002B002E00310000000000F8 +:10013000240027002A002D003000000000002300CA +:10014000260029002C002F000404040404030405E5 +:100150000202020204030202020206060606060664 +:10016000040402020204040408020110408040104A +:1001700020408040800802040180402010020110CD +:100180008010204040200000000200090F00000302 +:100190000401000C0000000000000000000000004E +:1001A0000000000000004D0811241FBECFEFDAE070 +:1001B000DEBFCDBF11E0A0E0B1E0E8E1F1E102C0B7 +:1001C00005900D92A233B107D9F721E0A2E3B1E087 +:1001D00001C01D92A93BB207E1F710E0C4EDD0E0E9 +:1001E00004C02197FE010E948408C33DD107C9F7CE +:1001F0000E94D6060C948A080C940000FC018091A1 +:100200006801882311F13FB7F89482E08093E900F8 +:100210002091F200822F90E01816190614F481E064 +:1002200090E0882339F0289A44E640936701409192 +:10023000F1004083222339F02091F200211103C004 +:100240002BE62093E8003FBF08958FEF9FEF0895BE +:100250002FB7F89483E08093E9009091E800892F0C +:10026000807295FF04C09091F20080E4891B2FBF3B +:10027000089580915F0181110DC082E080935B0140 +:1002800084E080935C0110925E0110925D0181E038 +:1002900080935F018BE591E00895282F30E0F9010C +:1002A000EA57FE4F9491F901E959FE4F8491285B7A +:1002B0003E4FF9012491222309F45BC09923E9F010 +:1002C00091509F30D0F4E92FF0E0E759FE4F0C94A5 +:1002D00084089401980178018C0190017D017D01D1 +:1002E0007D019B01A101A501A901AF017D01B30120 +:1002F000909180009F7790938000E22FF0E0EE0FC6 +:10030000FF1FE65CFE4FA591B491EC91E82381E0DC +:1003100090E089F580E00895909180009F7DEBCF7B +:1003200090918000977FE7CF94B59F7794BDE5CFFC +:1003300094B59F7DFBCF909190009F779093900014 +:10034000DCCF909190009F7DF9CF90919000977FA6 +:10035000F5CF9091C0009F779093C000CECF909141 +:10036000C0009F7DF9CF9091C200977F9093C2000B +:10037000C4CF80E090E008953FB7F8948091570192 +:1003800090915801A0915901B0915A0126B5A89BAE +:1003900005C02F3F19F00196A11DB11D3FBFBA2F17 +:1003A000A92F982F8827BC01CD01620F711D811DD7 +:1003B000911D42E0660F771F881F991F4A95D1F75C +:1003C00008958F929F92AF92BF92CF92DF92EF9259 +:1003D000FF924B015C010E94BC016B017C010E94F9 +:1003E000BC016C197D098E099F09683E7340810527 +:1003F0009105A8F328EEC20E23E0D21EE11CF11CE9 +:100400008A9489288A288B2829F0812C912C5401E0 +:100410008394E5CFFF90EF90DF90CF90BF90AF90A7 +:100420009F908F9008954091340150913501209113 +:1004300032013091330142175307B4F49091E80030 +:100440009570E1F39091E80092FD19C08093F1005E +:10045000809134019091350101968F739927892BF2 +:1004600019F48EEF8093E80080913401909135016A +:100470000196909335018093340181E0089580E0E6 +:100480000895EF92FF920F931F93CF93DF93F82E6F +:10049000192FE62E042F81E0860F880F0E94130289 +:1004A00083E00E941302CF2DD12FEC0EFD2EF11C04 +:1004B000CE15DF05B9F007FF13C0FE0184910E943D +:1004C0001302182F80E00E941302812321968111CC +:1004D000EFCFDF91CF911F910F91FF90EF90089593 +:1004E0008881EDCF81E0F5CFDF92EF92FF920F93FD +:1004F0001F93CF93DF93D82E8A01EB017B01E40E8B +:10050000F51ECE15DF0559F0D7FE12C0FE0184910D +:100510000E94130221968111F4CF0FEF1FEFC80143 +:10052000DF91CF911F910F91FF90EF90DF90089591 +:100530008881EECF0F931F93CF93DF931F92CDB798 +:10054000DEB782E0898342E450E06CEA70E080E844 +:100550000E9474020E943901DC0112960D911C91D7 +:100560000115110589F0D801ED91FC910280F3810C +:10057000E02DBE016F5F7F4FC801099597FD04C054 +:10058000F80100851185ECCF89810F90DF91CF9123 +:100590001F910F910895615030F02091F100FC01FE +:1005A00020830196F8CF289A84E680936701089506 +:1005B0008F929F92AF92BF92CF92DF92EF92FF9273 +:1005C0000F931F93CF93DF936C017B018A0180917E +:1005D0000B01882309F45CC080916801882309F429 +:1005E00057C08091380180FF05C08091E000826093 +:1005F0008093E000E801B12C8AEFA82E93E0892EC9 +:100600002AE3922E209711F4BB20D9F10E942801F1 +:1006100081110AC0AA94AA20D9F161E070E080E0BB +:1006200090E00E94E101EECF8C171D0611F00CF056 +:100630008C2F9FB7F8948092E9002091E80025FD67 +:1006400002C09FBFDFCF282F30E0C21BD30BF701C2 +:10065000815020F041914093F100FACFE20EF31E59 +:10066000BB2021F09092E800B12CEBCF8091E80004 +:1006700085FDE7CF9092E800BB24B394209709F35F +:10068000F3CF5D9A84E680933701101611063CF093 +:1006900081E090E0F6019383828310E000E0C801DE +:1006A000DF91CF911F910F91FF90EF90DF90CF904E +:1006B000BF90AF909F908F900895CF93DF931F923C +:1006C000CDB7DEB76983DC01ED91FC910280F38147 +:1006D000E02D41E050E0BE016F5F7F4F09950F9024 +:1006E000DF91CF91089583E08093E9008091F2003B +:1006F000882319F08AE38093E80008950E94280176 +:1007000090E00895CF93DF931F92CDB7DEB7FC0141 +:100710008485958597FD08C02FEF3FEF35872487A7 +:100720000F90DF91CF910895CE0101960E94FE00B7 +:10073000019719F4898190E0F3CF8FEF9FEFF0CF0D +:100740000F931F93CF93DF931F92CDB7DEB78C012A +:10075000FC018485958597FF0BC0CE0101960E9410 +:10076000FE00019771F4898190E0F80195878487F4 +:10077000F801848595850F90DF91CF911F910F919E +:1007800008958FEF9FEFF1CFFC018485958597FD4C +:100790000BC09FB7F89482E08093E9008091F2004B +:1007A0009FBF90E0019608959FB7F89482E08093F0 +:1007B000E9008091F2009FBF90E00895FC01019054 +:1007C0000020E9F73197AF01481B590BBC0189E6BE +:1007D00091E00C94D8021F920F920FB60F92112441 +:1007E0002F933F938F939F93AF93BF938091530128 +:1007F00090915401A0915501B09156013091520150 +:1008000023E0230F2D3758F50196A11DB11D20932C +:1008100052018093530190935401A0935501B093DA +:1008200056018091570190915801A0915901B091C2 +:100830005A010196A11DB11D809357019093580153 +:10084000A0935901B0935A01BF91AF919F918F919D +:100850003F912F910F900FBE0F901F90189526E893 +:10086000230F0296A11DB11DD2CF1F920F920FB67A +:100870000F9211248F939F938091E1009091E1005A +:10088000937F9093E10083FF0FC01092E90091E005 +:100890009093EB001092EC0092E39093ED00109295 +:1008A000680198E09093F00082FF22C093E090935B +:1008B000E9009091F200992319F09AE39093E800EF +:1008C00090913701992341F0909137019150909385 +:1008D0003701911101C05D9890916701992341F012 +:1008E00090916701915090936701911101C02898F0 +:1008F00084FF18C08091E2008E7E81608093E200C8 +:100900008091E1008F7E8093E100809138018E7E9E +:100910008061809338019F918F910F900FBE0F904F +:100920001F90189580FFF7CF8091E2008E7E806146 +:100930008093E2008091E1008E7E8093E1008091BF +:1009400038018E7E8160E5CF1F920F920FB60F9215 +:100950001124CF92DF92EF92FF920F931F932F9368 +:100960003F934F935F936F937F938F939F93AF9337 +:10097000BF93EF93FF93CF93DF93CDB7DEB76C9721 +:10098000DEBFCDBF1092E9008091E80083FF25C053 +:1009900068E0CE0145960E94CB0282EF8093E8008A +:1009A0008D8987FF39C09091E80090FFFCCF982F88 +:1009B000907609F034C19E894F89588D2F89F88C23 +:1009C000911131C0803861F5809139018093F10037 +:1009D0001092F1008EEF8093E8006C960FB6F894B9 +:1009E000DEBF0FBECDBFDF91CF91FF91EF91BF91E1 +:1009F000AF919F918F917F916F915F914F913F91B7 +:100A00002F911F910F91FF90EF90DF90CF900F905B +:100A10000FBE0F901F9018959EEF9093E800C7CFE0 +:100A20001092F100D5CF913059F48111D3CF4130DC +:100A3000510581F6809139018D7F80933901CACFAC +:100A4000933049F48111C6CF4130510519F6809198 +:100A500039018260F2CF953041F48091E80080FF47 +:100A6000FCCF20682093E300B5CF963009F0A9C0F1 +:100A70000B8D1C8D22E01092E9001092350110922E +:100A80003401F2122EC010923301109232010E94F2 +:100A90009A021F8299E09983FA8291E09E8390EAFC +:100AA00098879AEF99872091340130913501275F1B +:100AB0003F4F3C832B838D831092E90010923501C8 +:100AC00010923401109333010093320149E050E059 +:100AD000BE016F5F7F4F80E00E9474020E949A0205 +:100AE00079CF10933301009332010E943901DC0168 +:100AF00012960D911C910115110509F451C1D801EF +:100B0000ED91FC910480F581E02DBE016B5E7F4F7D +:100B1000C8010995009709F03EC1F80100851185CB +:100B2000EACFF3E0FF120EC08F89882309F440C09A +:100B3000823061F440E86DE080E191E00E94410282 +:100B4000811148CF81E28093EB0047CF813029F4B7 +:100B500040E86BE084E091E0F1CF833099F70E94A8 +:100B60003901DC011296ED90FC908E010F5F1F4F52 +:100B70006801E114F10479F0D701ED91FC91068050 +:100B8000F781E02DB801C7010995080F111DF70184 +:100B9000E084F184EECFD8011C92F6010190002090 +:100BA000E9F73197BF016C197D0940E0C601C6CF56 +:100BB0006EEE70E0FB01449150E080E80E94740208 +:100BC00009CF973009F4BECF983021F481E08093AB +:100BD000F10000CF993009F0FDCE837009F0B2CF5B +:100BE000EFE1F1E081E031E096E32191222371F021 +:100BF0008093E9003093EB00DF0111972C91209353 +:100C0000EC009093ED008F5F873079F78EE780934B +:100C1000EA001092EA008F8980936801DBCE8B8D09 +:100C20009C8D1092E900109235011092340190933E +:100C3000330180933201898D811192C08E899D8903 +:100C4000913A49F4813209F07DCF47E050E064E009 +:100C500071E080E0B3CF913209F074CF833269F450 +:100C60008F89988DB0E0A0E08093000190930101FE +:100C7000A0930201B0930301ADCE803269F480915C +:100C8000E80082FFFCCF67E084E091E00E94CB02A5 +:100C90008BEF8093E8009ECE823209F09BCE8F8945 +:100CA00080930B01EEEFFFE7859194918B3F9C4D74 +:100CB00051F1E0E0F8E08091040190910501A091EC +:100CC0000601B0910701803B9440A105B105F1F404 +:100CD00080910B0180FD1AC0EE3F8AE0F80789F58C +:100CE00087E797E791838083809160008093360146 +:100CF00088E19BE00FB6F894A895809360000FBE42 +:100D00009093600067CEEEEFFAE0D5CF80819181BD +:100D10008737974709F05ECEA8958091600088617B +:100D2000809360008091360180936000EE3F2AE05E +:100D3000F20789F08091FE0A9091FF0A91838083E7 +:100D400049CE808191818737980751F29093FF0AAD +:100D50008093FE0AC5CF1092FF0A1092FE0A3ACE87 +:100D60000E943901DC0112960D911C9101151105AB +:100D700009F4E8CED801ED91FC910190F081E02DCD +:100D8000BE016B5E7F4FC8010995811123CEF8012A +:100D900000851185EBCF181619060CF41BCED2CEA8 +:100DA000F1E0FF12BECE62EF70E004CFCF93DF938D +:100DB000CDB7DEB7A5970FB6F894DEBF0FBECDBF97 +:100DC000789484B5826084BD84B5816084BD85B526 +:100DD000826085BD85B5816085BD80916E00816032 +:100DE00080936E00109281008091810082608093D8 +:100DF00081008091810081608093810080918000DA +:100E00008160809380008091910082608093910046 +:100E10008091910081608093910080919000816029 +:100E2000809390008091C10084608093C100809184 +:100E3000C10082608093C1008091C1008160809375 +:100E4000C1008091C30081608093C3008091C00085 +:100E500082608093C0008091C20081608093C20054 +:100E600080917A00846080937A0080917A00826019 +:100E700080937A0080917A00816080937A008091DB +:100E80007A00806880937A0010926801109239018C +:100E9000109238018091D70081608093D70080EA5A +:100EA0008093D80089B5806189BD89B5826089BD8C +:100EB00009B400FEFDCF61E070E080E090E00E94A8 +:100EC000E1018091D8008F7C80618093D80080916F +:100ED000E000807F8093E0008091E1008E7E80932F +:100EE000E1008DE08093E200559A209A5D98289861 +:100EF0008FEF9FEF90937601809375010AE311E0E5 +:100F000055E2E52E51E0F52ED7018D917D01282F78 +:100F100030E0F901E959FE4F9491285B3E4FF90109 +:100F200024912223C9F030E0220F331FF901E45D40 +:100F3000FE4FA591B491F901E25EFE4F45915491A7 +:100F40003FB7F8946C91292F209526232C93DA0132 +:100F5000EC919E2B9C933FBF0E944D0121E0892B79 +:100F600009F420E03FB7F894809153019091540127 +:100F7000A0915501B09156013FBFF8012083218314 +:100F800082839383A483B5830A5F1F4FF9E2EF1630 +:100F9000F1E0FF0609F0B8CF9AE0492E512C612C00 +:100FA000712C8FB7F8948090530190905401A090C9 +:100FB0005501B09056018FBF0AE311E085E2282E5B +:100FC00081E0382ED101DD901D018D2D0E944D0153 +:100FD0009C01CC24C394892B09F4C12CF801808195 +:100FE000C81629F0C08282829382A482B582D80179 +:100FF00012964D915D916D917C911597D501C4012B +:10100000841B950BA60BB70B8897A105B10588F13A +:10101000D80111968C911197C81659F11196CC925E +:10102000882339F1232B29F589E291E00E94DE0320 +:101030002D2D30E050E040E019A26E01B1E2CB0E60 +:10104000D11CCA01B901A30192010E946208605D2E +:10105000F60162936F01211531054105510589F7AC +:10106000309719F0CF010E94DE038EE291E00E94DA +:10107000DE030A5F1F4FE214F30409F0A3CF62E01E +:1010800070E080E090E00E94E10140E050E0452BFC +:1010900009F487CF0E94000084CFE9E6F1E01382D3 +:1010A000128288EE93E0A0E0B0E084839583A6836B +:1010B000B78380E191E0918380838FEF9FEF9587E5 +:1010C00084870895A1E21A2EAA1BBB1BFD010DC047 +:1010D000AA1FBB1FEE1FFF1FA217B307E407F507E8 +:1010E00020F0A21BB30BE40BF50B661F771F881FC4 +:1010F000991F1A9469F760957095809590959B015A +:10110000AC01BD01CF010895EE0FFF1F0590F491D2 +:08111000E02D0994F894FFCFD3 +:10111800FFFFFFFF00E100000000000000000000EA +:101128005D03D8027E037303C4038203A00300C1D6 +:101138008081000000030507094750494F000D0A48 +:021148000000A5 +:00000001FF diff --git a/arduino/.pio/build/uno/idedata.json b/arduino/.pio/build/uno/idedata.json new file mode 100644 index 0000000..b54ac11 --- /dev/null +++ b/arduino/.pio/build/uno/idedata.json @@ -0,0 +1 @@ +{"build_type": "release", "env_name": "uno", "libsource_dirs": ["/home/christophe/Bureau/Prog/pySonnerie/arduino/lib", "/home/christophe/Bureau/Prog/pySonnerie/arduino/.pio/libdeps/uno", "/home/christophe/.platformio/lib", "/home/christophe/.platformio/packages/framework-arduino-avr/libraries"], "defines": ["PLATFORMIO=60119", "ARDUINO_AVR_MICRO", "F_CPU=16000000L", "ARDUINO_ARCH_AVR", "ARDUINO=10808", "USB_VID=0x2341", "USB_PID=0x8037", "USB_PRODUCT=\"Arduino Micro\"", "USB_MANUFACTURER=\"Arduino\"", "__AVR_ATmega32U4__"], "includes": {"build": ["/home/christophe/Bureau/Prog/pySonnerie/arduino/src", "/home/christophe/.platformio/packages/framework-arduino-avr/cores/arduino", "/home/christophe/.platformio/packages/framework-arduino-avr/variants/micro"], "compatlib": ["/home/christophe/.platformio/packages/framework-arduino-avr/libraries/EEPROM/src", "/home/christophe/.platformio/packages/framework-arduino-avr/libraries/HID/src", "/home/christophe/.platformio/packages/framework-arduino-avr/libraries/SPI/src", "/home/christophe/.platformio/packages/framework-arduino-avr/libraries/SoftwareSerial/src", "/home/christophe/.platformio/packages/framework-arduino-avr/libraries/Wire/src"], "toolchain": ["/home/christophe/.platformio/packages/toolchain-atmelavr/lib/gcc/avr/7.3.0/include-fixed", "/home/christophe/.platformio/packages/toolchain-atmelavr/lib/gcc/avr/7.3.0/include", "/home/christophe/.platformio/packages/toolchain-atmelavr/avr/include"]}, "cc_flags": ["-std=gnu11", "-fno-fat-lto-objects", "-mmcu=atmega32u4", "-Os", "-Wall", "-ffunction-sections", "-fdata-sections", "-flto"], "cxx_flags": ["-fno-exceptions", "-fno-threadsafe-statics", "-fpermissive", "-std=gnu++11", "-mmcu=atmega32u4", "-Os", "-Wall", "-ffunction-sections", "-fdata-sections", "-flto"], "cc_path": "/home/christophe/.platformio/packages/toolchain-atmelavr/bin/avr-gcc", "cxx_path": "/home/christophe/.platformio/packages/toolchain-atmelavr/bin/avr-g++", "gdb_path": "/home/christophe/.platformio/packages/toolchain-atmelavr/bin/avr-gdb", "prog_path": "/home/christophe/Bureau/Prog/pySonnerie/arduino/.pio/build/uno/firmware.elf", "svd_path": null, "compiler_type": "gcc", "targets": [{"name": "size", "title": "Program Size", "description": "Calculate program size", "group": "Platform"}, {"name": "upload", "title": "Upload", "description": null, "group": "Platform"}, {"name": "uploadeep", "title": "Upload EEPROM", "description": null, "group": "Platform"}, {"name": "fuses", "title": "Set Fuses", "description": null, "group": "Platform"}, {"name": "bootloader", "title": "Burn Bootloader", "description": null, "group": "Platform"}], "extra": {"flash_images": []}} \ No newline at end of file diff --git a/arduino/.pio/build/uno/libFrameworkArduino.a b/arduino/.pio/build/uno/libFrameworkArduino.a new file mode 100644 index 0000000..82a3c06 Binary files /dev/null and b/arduino/.pio/build/uno/libFrameworkArduino.a differ diff --git a/arduino/.pio/build/uno/libFrameworkArduinoVariant.a b/arduino/.pio/build/uno/libFrameworkArduinoVariant.a new file mode 100644 index 0000000..8b277f0 --- /dev/null +++ b/arduino/.pio/build/uno/libFrameworkArduinoVariant.a @@ -0,0 +1 @@ +! diff --git a/arduino/.pio/build/uno/src/main.cpp.o b/arduino/.pio/build/uno/src/main.cpp.o new file mode 100644 index 0000000..7a08a90 Binary files /dev/null and b/arduino/.pio/build/uno/src/main.cpp.o differ diff --git a/arduino/.vscode/c_cpp_properties.json b/arduino/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..f12af06 --- /dev/null +++ b/arduino/.vscode/c_cpp_properties.json @@ -0,0 +1,58 @@ +// +// !!! WARNING !!! AUTO-GENERATED FILE! +// PLEASE DO NOT MODIFY IT AND USE "platformio.ini": +// https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags +// +{ + "configurations": [ + { + "name": "PlatformIO", + "includePath": [ + "/home/christophe/Bureau/Prog/pySonnerie/arduino/src", + "/home/christophe/.platformio/packages/framework-arduino-avr/cores/arduino", + "/home/christophe/.platformio/packages/framework-arduino-avr/variants/micro", + "/home/christophe/.platformio/packages/framework-arduino-avr/libraries/EEPROM/src", + "/home/christophe/.platformio/packages/framework-arduino-avr/libraries/HID/src", + "/home/christophe/.platformio/packages/framework-arduino-avr/libraries/SPI/src", + "/home/christophe/.platformio/packages/framework-arduino-avr/libraries/SoftwareSerial/src", + "/home/christophe/.platformio/packages/framework-arduino-avr/libraries/Wire/src", + "" + ], + "browse": { + "limitSymbolsToIncludedHeaders": true, + "path": [ + "/home/christophe/Bureau/Prog/pySonnerie/arduino/src", + "/home/christophe/.platformio/packages/framework-arduino-avr/cores/arduino", + "/home/christophe/.platformio/packages/framework-arduino-avr/variants/micro", + "/home/christophe/.platformio/packages/framework-arduino-avr/libraries/EEPROM/src", + "/home/christophe/.platformio/packages/framework-arduino-avr/libraries/HID/src", + "/home/christophe/.platformio/packages/framework-arduino-avr/libraries/SPI/src", + "/home/christophe/.platformio/packages/framework-arduino-avr/libraries/SoftwareSerial/src", + "/home/christophe/.platformio/packages/framework-arduino-avr/libraries/Wire/src", + "" + ] + }, + "defines": [ + "PLATFORMIO=60119", + "ARDUINO_AVR_MICRO", + "F_CPU=16000000L", + "ARDUINO_ARCH_AVR", + "ARDUINO=10808", + "USB_VID=0x2341", + "USB_PID=0x8037", + "USB_PRODUCT=\"Arduino Micro\"", + "USB_MANUFACTURER=\"Arduino\"", + "__AVR_ATmega32U4__", + "" + ], + "cStandard": "gnu11", + "cppStandard": "gnu++11", + "compilerPath": "/home/christophe/.platformio/packages/toolchain-atmelavr/bin/avr-gcc", + "compilerArgs": [ + "-mmcu=atmega32u4", + "" + ] + } + ], + "version": 4 +} diff --git a/arduino/.vscode/launch.json b/arduino/.vscode/launch.json new file mode 100644 index 0000000..d0fe53b --- /dev/null +++ b/arduino/.vscode/launch.json @@ -0,0 +1,44 @@ +// AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY +// +// PlatformIO Debugging Solution +// +// Documentation: https://docs.platformio.org/en/latest/plus/debugging.html +// Configuration: https://docs.platformio.org/en/latest/projectconf/sections/env/options/debug/index.html + +{ + "version": "0.2.0", + "configurations": [ + { + "type": "platformio-debug", + "request": "launch", + "name": "PIO Debug", + "executable": "/home/christophe/Bureau/Prog/pySonnerie/arduino/.pio/build/uno/firmware.elf", + "projectEnvName": "uno", + "toolchainBinDir": "/home/christophe/.platformio/packages/toolchain-atmelavr/bin", + "internalConsoleOptions": "openOnSessionStart", + "preLaunchTask": { + "type": "PlatformIO", + "task": "Pre-Debug" + } + }, + { + "type": "platformio-debug", + "request": "launch", + "name": "PIO Debug (skip Pre-Debug)", + "executable": "/home/christophe/Bureau/Prog/pySonnerie/arduino/.pio/build/uno/firmware.elf", + "projectEnvName": "uno", + "toolchainBinDir": "/home/christophe/.platformio/packages/toolchain-atmelavr/bin", + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "platformio-debug", + "request": "launch", + "name": "PIO Debug (without uploading)", + "executable": "/home/christophe/Bureau/Prog/pySonnerie/arduino/.pio/build/uno/firmware.elf", + "projectEnvName": "uno", + "toolchainBinDir": "/home/christophe/.platformio/packages/toolchain-atmelavr/bin", + "internalConsoleOptions": "openOnSessionStart", + "loadMode": "manual" + } + ] +} diff --git a/backend/app/__init__.py b/backend/app/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/README_FRONTEND.md b/frontend/README_FRONTEND.md index f0e15ee..01cfa38 100644 --- a/frontend/README_FRONTEND.md +++ b/frontend/README_FRONTEND.md @@ -9,6 +9,34 @@ Frontend web responsive en Flask pour piloter le backend pySonnerie déjà en pl - Lancement manuel d'un trigger (`/api/play/{trigger_id}`) - Arrêt audio (`/api/stop`) - Gestion du stockage audio dans `backend/data/musiques` (televersement, telechargement, suppression) +- Import audio depuis un lien YouTube (extraction via `yt-dlp` puis conversion en MP3) + +## Prerequis + +- Python 3.11+ +- `yt-dlp` disponible dans l'environnement d'execution (installe via `pip install -r requirements.txt`) +- `ffmpeg` installe sur le systeme (requis par `yt-dlp` pour l'extraction audio) +- `node` installe sur le systeme (requis pour `--js-runtimes node`) +- Acces reseau sortant vers YouTube et GitHub (utilise par `--remote-components ejs:github`) +- Droits d'ecriture sur `backend/data/musiques` + +Installation recommandee (Debian/Ubuntu): + +```bash +sudo apt update +sudo apt install ffmpeg nodejs +``` + +Verification rapide des prerequis: + +```bash +source .venv/bin/activate +yt-dlp --version +node --version +ffmpeg -version +``` + +Note Debian/Ubuntu: si `node --version` echoue mais `nodejs --version` fonctionne, cree un alias ou un lien symbolique `node` vers `nodejs` pour respecter la commande d'extraction. ## Installation @@ -99,6 +127,12 @@ Le frontend sera alors servi par Gunicorn sur l'adresse definie par `FRONTEND_BI - Le frontend appelle le backend en HTTPS avec certificat autosigne (`verify=False`). - Les fichiers audio sont manipules localement dans `backend/data/musiques`. - Formats audio acceptes: `.mp3`, `.wav`, `.ogg`, `.flac`, `.aac`, `.m4a`. +- Depuis la page de stockage audio, un champ URL permet d'importer l'audio d'une video YouTube. +- La commande utilisee pour l'extraction est: + +```bash +yt-dlp --js-runtimes node --remote-components ejs:github -x --audio-format mp3 URL +``` ## Changelog diff --git a/frontend/app/__init__.py b/frontend/app/__init__.py deleted file mode 100644 index 4078aa8..0000000 --- a/frontend/app/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import json -from pathlib import Path - -from flask import Flask - - -def _load_secret_key(project_root: Path) -> str: - conf_path = project_root / "frontend" / "data" / "conf.json" - if conf_path.exists(): - try: - conf = json.loads(conf_path.read_text(encoding="utf-8")) - key = conf.get("secret_key", "") - if key: - return str(key) - except Exception: - pass - return "pysonnerie-frontend-dev-key" - - -def create_app() -> Flask: - app = Flask(__name__) - - project_root = Path(__file__).resolve().parents[2] - secret = _load_secret_key(project_root) - app.config["SECRET_KEY"] = secret - - app.config["PROJECT_ROOT"] = project_root - app.config["MUSIC_DIR"] = project_root / "backend" / "data" / "musiques" - app.config["MAX_CONTENT_LENGTH"] = 128 * 1024 * 1024 - - from .routes import ui - - app.register_blueprint(ui) - return app diff --git a/frontend/app/__pycache__/__init__.cpython-312.pyc b/frontend/app/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index b6997d1..0000000 Binary files a/frontend/app/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/frontend/app/__pycache__/backend_client.cpython-312.pyc b/frontend/app/__pycache__/backend_client.cpython-312.pyc deleted file mode 100644 index 9b896cb..0000000 Binary files a/frontend/app/__pycache__/backend_client.cpython-312.pyc and /dev/null differ diff --git a/frontend/app/__pycache__/routes.cpython-312.pyc b/frontend/app/__pycache__/routes.cpython-312.pyc deleted file mode 100644 index c1e478b..0000000 Binary files a/frontend/app/__pycache__/routes.cpython-312.pyc and /dev/null differ diff --git a/frontend/app/routes.py b/frontend/app/routes.py index 116cf78..0323e6e 100644 --- a/frontend/app/routes.py +++ b/frontend/app/routes.py @@ -1,6 +1,10 @@ from __future__ import annotations import re +import shutil +import subprocess +import threading +import uuid from pathlib import Path from flask import ( @@ -25,6 +29,19 @@ ui = Blueprint("ui", __name__, template_folder="templates") GPIO_PATTERN = re.compile(r"^GPIO\d+$") ALLOWED_AUDIO_EXTENSIONS = {".mp3", ".wav", ".ogg", ".flac", ".aac", ".m4a"} +YOUTUBE_PROGRESS_PATTERN = re.compile(r"(\d+(?:\.\d+)?)%") +YOUTUBE_POSTPROCESS_HINTS = ( + "extractaudio", + "ffmpeg", + "postprocess", + "post-process", + "merging", + "deleting original file", + "destination", +) +INVALID_FILENAME_CHARS_PATTERN = re.compile(r"[<>:\"/\\|?*\x00-\x1f]") +_YOUTUBE_JOBS_LOCK = threading.Lock() +_YOUTUBE_JOBS: dict[str, dict[str, object]] = {} def _backend_client() -> BackendClient | None: @@ -58,6 +75,17 @@ def _list_audio_files() -> list[str]: return sorted(files, key=str.lower) +def _used_audio_files_from_triggers(triggers: dict[str, dict]) -> set[str]: + used: set[str] = set() + for trigger in triggers.values(): + if not isinstance(trigger, dict): + continue + music_file = str(trigger.get("music_file", "")).strip() + if music_file: + used.add(Path(music_file).name.lower()) + return used + + def _parse_optional_float(value: str) -> float | None: clean = value.strip() if clean == "": @@ -83,6 +111,170 @@ def _audio_redirect_target() -> str: return url_for("ui.dashboard") +def _normalize_youtube_filename(raw_name: str) -> str | None: + # Keep readable names (including spaces) but strip dangerous/path chars. + candidate = Path(raw_name.strip()).name + if candidate == "": + return None + + stem = Path(candidate).stem + stem = INVALID_FILENAME_CHARS_PATTERN.sub(" ", stem) + stem = re.sub(r"\s+", " ", stem).strip(" .") + if stem == "": + return None + return f"{stem}.mp3" + + +def _next_available_filename(filename: str, existing_names: set[str]) -> str: + candidate = filename + stem = Path(filename).stem + suffix = Path(filename).suffix or ".mp3" + index = 2 + while candidate.lower() in existing_names: + candidate = f"{stem}_{index}{suffix}" + index += 1 + return candidate + + +def _build_youtube_download_command(youtube_url: str, requested_name: str, music_dir: Path) -> tuple[list[str] | None, str | None]: + yt_dlp_path = shutil.which("yt-dlp") + if yt_dlp_path is None: + return None, "yt-dlp est introuvable sur le système." + + if shutil.which("node") is None: + return None, "node est requis pour l'option --js-runtimes node." + + command = [ + yt_dlp_path, + "--js-runtimes", + "node", + "--remote-components", + "ejs:github", + "--newline", + "-x", + "--audio-format", + "mp3", + ] + + if requested_name: + normalized_name = _normalize_youtube_filename(requested_name) + if normalized_name is None: + return None, "Nom de fichier YouTube invalide." + + existing_names = {item.name.lower() for item in music_dir.iterdir() if item.is_file()} + if normalized_name.lower() in existing_names: + return None, "Un fichier avec ce nom existe déjà." + + output_template = music_dir / f"{Path(normalized_name).stem}.%(ext)s" + command.extend(["-o", str(output_template)]) + else: + command.extend(["-P", str(music_dir)]) + + command.append(youtube_url) + return command, None + + +def _extract_progress_percent(line: str) -> float | None: + match = YOUTUBE_PROGRESS_PATTERN.search(line) + if match is None: + return None + try: + value = float(match.group(1)) + except ValueError: + return None + return max(0.0, min(100.0, value)) + + +def _set_youtube_job_state(job_id: str, **updates: object) -> None: + with _YOUTUBE_JOBS_LOCK: + current = _YOUTUBE_JOBS.get(job_id) + if current is None: + return + current.update(updates) + + +def _create_youtube_job() -> str: + job_id = uuid.uuid4().hex + with _YOUTUBE_JOBS_LOCK: + _YOUTUBE_JOBS[job_id] = { + "status": "running", + "percent": 0.0, + "message": "Préparation du téléchargement...", + } + return job_id + + +def _run_youtube_download_job(job_id: str, command: list[str]) -> None: + try: + process = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + ) + except OSError as exc: + _set_youtube_job_state(job_id, status="error", message=f"Impossible d'exécuter yt-dlp: {exc}") + return + + last_line = "" + max_percent = 0.0 + in_postprocess = False + output = process.stdout + if output is not None: + for raw_line in output: + line = raw_line.strip() + if line == "": + continue + last_line = line + lower_line = line.lower() + + if any(hint in lower_line for hint in YOUTUBE_POSTPROCESS_HINTS): + in_postprocess = True + shown_percent = max(max_percent, 99.0) + _set_youtube_job_state( + job_id, + status="running", + percent=shown_percent, + message="Conversion audio en cours...", + ) + + progress = _extract_progress_percent(line) + if progress is None: + continue + if progress > max_percent: + max_percent = progress + + shown_percent = min(max_percent, 99.0) + if shown_percent >= 99.0: + in_postprocess = True + + message = ( + "Conversion audio en cours..." + if in_postprocess + else f"Téléchargement en cours: {shown_percent:.1f}%" + ) + _set_youtube_job_state( + job_id, + status="running", + percent=shown_percent, + message=message, + ) + + return_code = process.wait() + if return_code == 0: + _set_youtube_job_state( + job_id, + status="completed", + percent=100.0, + message="Audio YouTube extrait et ajouté au stockage.", + ) + return + + detail = last_line if last_line else "Erreur inconnue" + _set_youtube_job_state(job_id, status="error", message=f"Échec extraction YouTube: {detail}") + + def _trigger_sort_key(item: tuple[str, dict]) -> tuple[int, int | str]: trigger_id = item[0] if trigger_id.isdigit(): @@ -144,10 +336,12 @@ def dashboard() -> str | Response: client = client_or_redirect triggers: dict[str, dict] = {} + used_audio_files: set[str] = set() try: raw = client.list_triggers() valid_triggers = {k: v for k, v in raw.items() if isinstance(v, dict)} triggers = dict(sorted(valid_triggers.items(), key=_trigger_sort_key)) + used_audio_files = _used_audio_files_from_triggers(valid_triggers) except BackendApiError as exc: flash(f"Impossible de charger les triggers: {exc}", "error") @@ -157,6 +351,7 @@ def dashboard() -> str | Response: "dashboard.html", triggers=triggers, audio_files=audio_files, + used_audio_files=used_audio_files, backend_url=session.get("backend_url", ""), username=session.get("backend_username", ""), ) @@ -168,8 +363,16 @@ def audio_storage() -> str | Response: if isinstance(client_or_redirect, Response): return client_or_redirect + client = client_or_redirect audio_files = _list_audio_files() - return render_template("audio_storage.html", audio_files=audio_files) + used_audio_files: set[str] = set() + try: + raw = client.list_triggers() + valid_triggers = {k: v for k, v in raw.items() if isinstance(v, dict)} + used_audio_files = _used_audio_files_from_triggers(valid_triggers) + except BackendApiError as exc: + flash(f"Impossible de charger les triggers: {exc}", "error") + return render_template("audio_storage.html", audio_files=audio_files, used_audio_files=used_audio_files) @ui.post("/trigger/save") @@ -353,6 +556,29 @@ def upload_audio() -> Response: if isinstance(client_or_redirect, Response): return client_or_redirect + youtube_url = request.form.get("youtube_url", "").strip() + if youtube_url: + requested_name = request.form.get("youtube_filename", "").strip() + command, command_error = _build_youtube_download_command(youtube_url, requested_name, _music_dir()) + if command is None: + flash(command_error or "Impossible de préparer la commande yt-dlp.", "error") + return redirect(_audio_redirect_target()) + + try: + result = subprocess.run(command, check=False, capture_output=True, text=True) + except OSError as exc: + flash(f"Impossible d'exécuter yt-dlp: {exc}", "error") + return redirect(_audio_redirect_target()) + + if result.returncode != 0: + detail = (result.stderr or result.stdout or "").strip().splitlines() + message = detail[-1] if detail else "Erreur inconnue" + flash(f"Échec extraction YouTube: {message}", "error") + return redirect(_audio_redirect_target()) + + flash("Audio YouTube extrait et ajouté au stockage.", "success") + return redirect(_audio_redirect_target()) + audio = request.files.get("audio_file") if audio is None or audio.filename is None or audio.filename.strip() == "": flash("Sélectionnez d'abord un fichier.", "error") @@ -395,11 +621,111 @@ def delete_audio() -> Response: flash("Fichier introuvable.", "error") return redirect(_audio_redirect_target()) + client = client_or_redirect + try: + raw = client.list_triggers() + valid_triggers = {k: v for k, v in raw.items() if isinstance(v, dict)} + used_audio_files = _used_audio_files_from_triggers(valid_triggers) + if Path(filename).name.lower() in used_audio_files: + flash("Ce fichier est associé à un trigger et ne peut pas être supprimé.", "error") + return redirect(_audio_redirect_target()) + except BackendApiError as exc: + flash(f"Impossible de vérifier les triggers: {exc}", "error") + return redirect(_audio_redirect_target()) + target.unlink() flash(f"Fichier {filename} supprimé.", "success") return redirect(_audio_redirect_target()) +@ui.post("/audio/youtube/proposed-name") +def youtube_proposed_name() -> Response: + client_or_redirect = _ensure_login() + if isinstance(client_or_redirect, Response): + return jsonify({"ok": False, "error": "Non connecte"}), 401 + + youtube_url = request.form.get("youtube_url", "").strip() + if youtube_url == "": + return jsonify({"ok": False, "error": "Lien YouTube manquant."}), 400 + + yt_dlp_path = shutil.which("yt-dlp") + if yt_dlp_path is None: + return jsonify({"ok": False, "error": "yt-dlp est introuvable sur le système."}), 400 + if shutil.which("node") is None: + return jsonify({"ok": False, "error": "node est requis pour l'option --js-runtimes node."}), 400 + + command = [ + yt_dlp_path, + "--js-runtimes", + "node", + "--remote-components", + "ejs:github", + "--no-playlist", + "--skip-download", + "--print", + "%(title)s", + youtube_url, + ] + + try: + result = subprocess.run(command, check=False, capture_output=True, text=True) + except OSError as exc: + return jsonify({"ok": False, "error": f"Impossible d'exécuter yt-dlp: {exc}"}), 500 + + if result.returncode != 0: + detail = (result.stderr or result.stdout or "").strip().splitlines() + message = detail[-1] if detail else "Erreur inconnue" + return jsonify({"ok": False, "error": f"Échec lecture metadonnées YouTube: {message}"}), 502 + + title = next((line.strip() for line in result.stdout.splitlines() if line.strip()), "") + proposed = _normalize_youtube_filename(title) or "audio_youtube.mp3" + + existing_names = {item.name.lower() for item in _music_dir().iterdir() if item.is_file()} + available_name = _next_available_filename(proposed, existing_names) + return jsonify({"ok": True, "filename": available_name}) + + +@ui.post("/audio/youtube/start") +def youtube_download_start() -> Response: + client_or_redirect = _ensure_login() + if isinstance(client_or_redirect, Response): + return jsonify({"ok": False, "error": "Non connecte"}), 401 + + youtube_url = request.form.get("youtube_url", "").strip() + if youtube_url == "": + return jsonify({"ok": False, "error": "Lien YouTube manquant."}), 400 + + requested_name = request.form.get("youtube_filename", "").strip() + music_dir = _music_dir() + command, command_error = _build_youtube_download_command(youtube_url, requested_name, music_dir) + if command is None: + return jsonify({"ok": False, "error": command_error or "Impossible de préparer la commande yt-dlp."}), 400 + + job_id = _create_youtube_job() + worker = threading.Thread(target=_run_youtube_download_job, args=(job_id, command), daemon=True) + worker.start() + return jsonify({"ok": True, "job_id": job_id}) + + +@ui.get("/audio/youtube/status/") +def youtube_download_status(job_id: str) -> Response: + client_or_redirect = _ensure_login() + if isinstance(client_or_redirect, Response): + return jsonify({"ok": False, "error": "Non connecte"}), 401 + + with _YOUTUBE_JOBS_LOCK: + job = _YOUTUBE_JOBS.get(job_id) + if job is None: + return jsonify({"ok": False, "error": "Téléchargement introuvable."}), 404 + payload = { + "ok": True, + "status": str(job.get("status", "running")), + "percent": float(job.get("percent", 0.0)), + "message": str(job.get("message", "Téléchargement en cours...")), + } + return jsonify(payload) + + @ui.get("/audio/download/") def download_audio(filename: str) -> Response: client_or_redirect = _ensure_login() diff --git a/frontend/app/templates/audio_storage.html b/frontend/app/templates/audio_storage.html index 74289a5..cd97e03 100644 --- a/frontend/app/templates/audio_storage.html +++ b/frontend/app/templates/audio_storage.html @@ -19,12 +19,20 @@ +
+ + + + +
+ {% if audio_files %}
    {% for filename in audio_files %}
  • {{ filename }}
    + {% set is_used_by_trigger = filename|lower in used_audio_files %} +
  • @@ -59,6 +71,45 @@ + + + + + +