├── requirements.txt ├── README.md ├── keyword.txt └── download_random_image.py /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.28.0 2 | Pillow>=9.0.0 3 | colorama>=0.4.4 4 | tabulate>=0.8.9 5 | psutil>=5.9.0 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auto Generator Image 10KB 2 | 3 | ![Version](https://img.shields.io/badge/version-1.0.0-blue) 4 | ![License](https://img.shields.io/badge/license-MIT-green) 5 | ![Python](https://img.shields.io/badge/python-3.6%2B-blue) 6 | 7 | Công cụ tự động tải và tối ưu hóa hình ảnh với kích thước dưới 10KB từ nhiều nguồn khác nhau. Chương trình này hỗ trợ tải hình ảnh đồng thời từ nhiều API và tự động nén hình ảnh xuống còn dưới 10KB mà vẫn giữ được chất lượng tốt nhất có thể. 8 | 9 | ## Tính năng chính 10 | 11 | - **Tải ảnh từ nhiều nguồn khác nhau:** 12 | 13 | - [Pexels](https://www.pexels.com/) - Kho ảnh chuyên nghiệp với nhiều chủ đề 14 | - [ThisPersonDoesNotExist](https://thispersondoesnotexist.com/) - Ảnh khuôn mặt AI tạo ra 15 | - [Picsum Photos](https://picsum.photos/) - Ảnh ngẫu nhiên đa dạng 16 | - [TheCatAPI](https://thecatapi.com/) - Ảnh mèo đáng yêu 17 | 18 | - **Tối ưu hóa hình ảnh:** 19 | 20 | - Nén ảnh xuống dưới 10KB 21 | - Giảm kích thước thông minh 22 | - Lựa chọn chất lượng tối ưu bằng thuật toán tìm kiếm nhị phân 23 | 24 | - **Xử lý đa luồng:** 25 | - Tải nhiều ảnh cùng lúc 26 | - Tải song song từ nhiều nguồn 27 | - Điều chỉnh số lượng luồng cho mỗi nguồn 28 | - **Theo dõi tiến trình:** 29 | - Hiển thị thống kê chi tiết 30 | - Ước tính thời gian hoàn thành 31 | - Tỷ lệ thành công theo nguồn 32 | - **Chức năng tiên tiến:** 33 | - Phát hiện và bỏ qua ảnh trùng lặp bằng perceptual hashing 34 | - Điều chỉnh tải CPU tự động 35 | - Xử lý giới hạn rate limit của API 36 | - Chế độ debug để khắc phục sự cố 37 | 38 | ## Yêu cầu hệ thống 39 | 40 | - Python 3.6 trở lên 41 | - Các thư viện phụ thuộc (được cài đặt tự động): 42 | - requests 43 | - Pillow (PIL) 44 | - colorama 45 | - tabulate 46 | - psutil (tùy chọn, để giám sát CPU) 47 | 48 | ## Cài đặt 49 | 50 | 1. Clone repository: 51 | 52 | ```bash 53 | git clone https://github.com/hasoftware/Download-Random-Image.git 54 | cd Download-Random-Image 55 | ``` 56 | 57 | 2. Cài đặt các thư viện cần thiết: 58 | 59 | ```bash 60 | pip install -r requirements.txt 61 | ``` 62 | 63 | 3. (Tùy chọn) Tạo file `keyword.txt` chứa danh sách từ khóa tìm kiếm cho Pexels, mỗi từ khóa cách nhau bằng dấu phẩy: 64 | 65 | ``` 66 | landscape, nature, mountain, beach, sunset, forest, ocean, waterfall, desert, city 67 | ``` 68 | 69 | ## Hướng dẫn sử dụng 70 | 71 | 1. Chạy chương trình: 72 | 73 | ```bash 74 | python download_random_image.py 75 | ``` 76 | 77 | 2. Cấu hình các tùy chọn: 78 | 79 | - Bật/tắt chế độ debug 80 | - Bật/tắt chế độ điều chỉnh CPU tự động 81 | - Chọn các API muốn sử dụng (Pexels, ThisPersonDoesNotExist, Picsum, TheCatAPI) 82 | - Cấu hình tốc độ tải xuống và số luồng cho mỗi API 83 | - Nhập tổng số lượng ảnh muốn tải 84 | 85 | 3. Theo dõi tiến trình: 86 | - Chương trình sẽ hiển thị bảng tiến độ 87 | - Thống kê theo từng nguồn 88 | - Ước tính thời gian hoàn thành 89 | 90 | ## Cấu trúc thư mục 91 | 92 | ``` 93 | Download-Random-Image/ 94 | │ 95 | ├── download_random_image.py # Mã nguồn chính 96 | ├── keyword.txt # Danh sách từ khóa tìm kiếm cho Pexels 97 | ├── requirements.txt # Danh sách thư viện cần thiết 98 | ├── README.md # Tài liệu hướng dẫn 99 | │ 100 | ├── images/ # Thư mục lưu ảnh đã tải (tạo tự động) 101 | └── output/ # Thư mục đầu ra (tạo tự động) 102 | ``` 103 | 104 | ## API Key 105 | 106 | Chương trình đã được cấu hình với API key mặc định cho Pexels và TheCatAPI. Tuy nhiên, bạn có thể sử dụng API key riêng của mình: 107 | 108 | - **Pexels**: Đăng ký tại [Pexels API](https://www.pexels.com/api/) và cập nhật trong mã nguồn 109 | - **TheCatAPI**: Đăng ký tại [TheCatAPI](https://thecatapi.com/) hoặc nhập key khi chạy chương trình 110 | 111 | ## Chi tiết kỹ thuật 112 | 113 | ### Thuật toán tối ưu hóa hình ảnh 114 | 115 | 1. **Phát hiện trùng lặp bằng perceptual hashing:** 116 | - Chuyển đổi ảnh sang grayscale 117 | - Resize xuống 8x8 pixel 118 | - Tính ngưỡng độ sáng trung bình 119 | - Tạo hash nhị phân dựa trên so sánh độ sáng 120 | 2. **Thuật toán tìm kiếm nhị phân cho chất lượng tối ưu:** 121 | - Thử nghiệm các mức chất lượng nén từ 5% đến 95% 122 | - Sử dụng tìm kiếm nhị phân để nhanh chóng tìm mức chất lượng cao nhất mà vẫn dưới 10KB 123 | 3. **Tối ưu hóa kích thước ảnh:** 124 | - Resize ảnh theo tỷ lệ khung hình 125 | - Tăng độ tương phản và độ sáng 126 | - Áp dụng bộ lọc làm sắc nét 127 | 128 | ### Quản lý đa luồng 129 | 130 | - Sử dụng `ThreadPoolExecutor` để tạo và quản lý luồng 131 | - Khóa đồng bộ hóa (`threading.Lock`) để bảo vệ tài nguyên dùng chung 132 | - Quản lý lồng nhau các executor để kiểm soát tốc độ và tải 133 | 134 | ### Giám sát hiệu suất 135 | 136 | - Đo thời gian hoàn thành 137 | - Ước tính thời gian còn lại 138 | - Tự động điều chỉnh tốc độ dựa trên tải CPU 139 | 140 | ## Điều chỉnh hiệu suất 141 | 142 | - **Tốc độ tải xuống**: Tăng/giảm số ảnh mỗi giây cho từng API 143 | - **Số luồng**: Điều chỉnh số luồng đồng thời cho mỗi API 144 | - **Ngưỡng CPU**: Thiết lập ngưỡng CPU tối đa và mức mục tiêu 145 | - **Chế độ debug**: Bật để xem thông tin chi tiết về quá trình tải xuống 146 | 147 | ## Đóng góp 148 | 149 | Đóng góp và báo cáo lỗi rất được hoan nghênh! Vui lòng tạo issue hoặc pull request trên GitHub. 150 | 151 | ## Giấy phép 152 | 153 | Dự án này được phân phối dưới giấy phép MIT. Xem file `LICENSE` để biết thêm chi tiết. 154 | -------------------------------------------------------------------------------- /keyword.txt: -------------------------------------------------------------------------------- 1 | Animator, Architect, Interior designer, Construction worker, Builder, Electrician, Plumber, Carpenter, Welder, Mechanic, Auto mechanic, Driver, Truck driver, Taxi driver, Delivery driver, Pilot, Flight attendant, Air traffic controller, Sailor, Fisherman, Farmer, Gardener, Florist, Chef, Cook, Baker, Barista, Waiter, Waitress, Bartender, Hotel staff, Housekeeper, Tour guide, Travel agent, Photographer, Videographer, Film director, Actor, Actress, Model, Musician, Singer, DJ, Dancer, Artist, Painter, Sculptor, Writer, Author, Journalist, Editor, Blogger, Vlogger, Influencer, Podcaster, Teacher, Professor, Tutor, School principal, Librarian, Researcher, Student, Kindergarten teacher, Coach, Personal trainer, Fitness instructor, Yoga teacher, Life coach, Counselor, Social worker, Police officer, Firefighter, Security guard, Soldier, Army officer, Navy officer, Lawyer, Judge, Paralegal, Notary, Government official, Diplomat, Politician, Economist, Banker, Financial analyst, Accountant, Auditor, Tax advisor, Real estate agent, Property manager, Insurance agent, Stock trader, Investment banker, Entrepreneur, CEO, Manager, HR manager, Recruiter, Salesperson, Marketer, Digital marketer, SEO specialist, PR manager, Customer service, Call center agent, Event planner, Wedding planner, Makeup artist, Hair stylist, Fashion designer, Tailor, Retail worker, Store manager, Cashier, Cleaner, Janitor, Factory worker, Warehouse worker, Logistician, Supply chain manager, NGO worker, Humanitarian worker, Environmentalist, Climate scientist, Archaeologist, Historian, Museum curator, Astronomer, Space scientist, Astronaut, Game developer, Game designer, Tester, E-sports player, Streamer, Voice actor, Translator, Interpreter, Sign language interpreter, Customs officer, Immigration officer, Zoo keeper, Animal trainer, Dog walker, Pet groomer, Babysitter, Nanny, Elderly caregiver, Therapist, Hypnotherapist, Nutritionist, Dietitian, Chiropractor, Acupuncturist, Tattoo artist, Piercer, Crystal healer, Astrologer, Monk, Nun, Religious leader, Missionary, Charity worker, Woman, Women, Girl, Girls, Beautiful woman, Happy woman, Confident woman, Strong woman, Elegant woman, Smiling girl, Laughing girl, Female portrait, Asian woman, Black woman, White woman, Latina woman, Indian woman, Arab woman, African woman, Middle Eastern woman, European woman, Mature woman, Young woman, Teen girl, Stylish woman, Fashion woman, Woman in dress, Woman in hijab, Businesswoman, Working woman, Fitness woman, Woman yoga, Running woman, Woman in nature, Woman with flowers, Woman with long hair, Short hair woman, Curly hair woman, Braided hair, Redhead woman, Woman in glasses, Woman reading, Woman with dog, Woman with cat, Woman traveler, Woman on beach, Woman in mountains, Woman in forest, Urban woman, City girl, Modern woman, Vintage look woman, Natural look, Minimalist woman, Artistic woman, Glamorous woman, Makeup woman, Woman with coffee, Woman shopping, Woman laughing, Woman relaxing, Dreamy girl, Empowered woman, Feminist, Women together, Girl gang, Best friends female, Mother and daughter, Bride, Bride to be, Wedding dress, Woman dancing, Woman singing, Woman cooking, Woman working from home, Remote work female, Female freelancer, Creative woman, Inspiring woman, Pregnant woman, Motherhood, Baby girl, Girl playing, Girl learning, Woman meditating, Woman painting, Woman drawing, Woman photographing, Woman with camera, Adventurous woman, Solo traveler woman, Beach girl, Stylish teen girl, Female expression, Emotional woman, Woman at sunset, Mysterious woman,Man, Men, Boy, Boys, Handsome man, Happy man, Strong man, Confident man, Smiling man, Laughing man, Male portrait, Asian man, Black man, White man, Latino man, Indian man, Arab man, African man, European man, Middle Eastern man, Mature man, Young man, Teen boy, Stylish man, Fashion man, Man in suit, Man in t-shirt, Businessman, Working man, Fitness man, Man running, Man at gym, Man with beard, Bearded man, Man with tattoos, Man with glasses, Bald man, Man with hat, Man reading, Man with dog, Man with cat, Man traveler, Man hiking, Man on beach, Man in mountains, Man in forest, Urban man, City guy, Casual guy, Modern man, Cool guy, Man with guitar, Man singing, Man cooking, Man with coffee, Man working from home, Remote work male, Male freelancer, Man relaxing, Man meditating, Man thinking, Creative man, Artistic man, Adventurous man, Solo traveler man, Man fishing, Man camping, Man photographing, Man with camera, Man on motorbike, Street man, Skater boy, Sportsman, Football player, Basketball player, Father and son, Groom, Groomsman, Man in wedding, Husband, Boyfriend, Best friends male, Group of guys, Brothers, Emotional man, Man crying, Angry man, Joyful man, Focused man, Determined man, Tough man, Peaceful man, Traditional man, Cultural man, Global men, International man, Global traveler man, Natural look man, Urban lifestyle man, Man with style, Charismatic man, Cultural woman, Traditional dress, Global women, International woman, Beautiful smile woman, Nature, Landscape, Forest, Trees, Sunset, Sunrise, Mountain, Ocean, Sea, Beach, Lake, River, Waterfall, Sky, Clouds, Stars, Moon, Sunlight, Desert, Snow, Ice, Grass, Flowers, Leaves, Rocks, Cliff, Canyon, Valley, Fog, Mist, Thunderstorm, Rain, Rainbow, Wind, Sand, Volcano, Aurora, Night sky, Starry sky, City, Urban, Street, Road, Bridge, Building, Architecture, Skyscraper, House, Apartment, Interior, Room, Living room, Bedroom, Kitchen, Bathroom, Window, Door, Balcony, Garden, Fence, Staircase, Ceiling, Wall, Floor, Lamp, Chair, Table, Sofa, Bed, Mirror, Carpet, Decor, Furniture, Office, Workspace, Desk, Laptop, Computer, Phone, Tablet, Technology, Coding, Programmer, UX design, App, Website, AI, Robot, Circuit, Electronics, VR, AR, Gaming, Console, Controller, Esports, Gamer, Keyboard, Mouse, Headphones, Monitor, Smartwatch, Charging, Electricity, Cable, Screen, Tech setup, Portrait, People, Man, Woman, Couple, Friends, Family, Kids, Child, Baby, Teen, Elderly, Smile, Laugh, Eyes, Hair, Hands, Feet, Face, Pose, Fashion, Model, Clothes, Dress, Shirt, Pants, Shoes, Heels, Sneakers, Hat, Sunglasses, Watch, Jewelry, Earrings, Makeup, Lipstick, Skin, Skincare, Perfume, Street fashion, Runway, Urban style, Casual wear, Formal wear, Workout clothes, Sportswear, Fitness, Gym, Exercise, Workout, Yoga, Meditation, Healthy, Running, Jogging, Sports, Football, Basketball, Tennis, Baseball, Volleyball, Swimming, Hiking, Climbing, Camping, Adventure, Travel, Tourist, Backpack, Luggage, Airplane, Airport, Hotel, Resort, Beach trip, Road trip, Map, Compass, Culture, Tradition, Festival, Ceremony, Religion, Church, Mosque, Temple, Monastery, Cross, Bible, Quran, Buddha, Meditation, Zen, Prayer, Candle, Incense, Food, Meal, Breakfast, Lunch, Dinner, Snack, Dessert, Cake, Cupcake, Donut, Cookie, Pizza, Burger, Sandwich, Sushi, Ramen, Pasta, Noodles, Steak, Chicken, Fish, Egg, Cheese, Salad, Fruit, Apple, Banana, Orange, Grapes, Watermelon, Strawberry, Cherry, Pineapple, Avocado, Vegetable, Carrot, Broccoli, Tomato, Onion, Garlic, Drink, Coffee, Tea, Latte, Espresso, Juice, Wine, Beer, Cocktail, Smoothie, Milkshake, Water bottle, Kitchenware, Plate, Bowl, Spoon, Fork, Knife, Cooking, Baking, Chef, Recipe, Ingredients, Market, Supermarket, Grocery, Organic, Vegan, Vegetarian, Meat, BBQ, Food styling, Flat lay, Hands holding food, Cafe, Restaurant, Coffee shop, Menu, Bill, Payment, Tipping, Bar, Nightlife, Party, Dance, Music, Concert, DJ, Band, Guitar, Piano, Violin, Drums, Microphone, Song, Album, Playlist, Headphones, Speaker, Music studio, Singing, Listening, Relaxing, Chill, Mood, Emotion, Happy, Sad, Angry, Love, Romance, Kiss, Hug, Holding hands, Proposal, Wedding, Bride, Groom, Rings, Dress, Tuxedo, Marriage, Honeymoon, Baby shower, Birthday, Celebration, Gift, Surprise, Balloons, Candles, Confetti, New year, Christmas, Halloween, Easter, Thanksgiving, Valentine, Fireworks, Decorations, Lights, DIY, Craft, Art, Drawing, Painting, Sketch, Brush, Pencil, Canvas, Studio, Artist, Tattoo, Graffiti, Photography, Camera, Lens, Tripod, Filter, Editing, Lightroom, Photoshop, Snap, Selfie, Mirror selfie, Shadow, Silhouette, Motion blur, Minimalist, Black and white, Colorful, Aesthetic, Moodboard, Visual, Graphic design, Font, Typography, Poster, Flyer, Banner, Illustration, Animation, 3D model, Mockup, Branding, Logo, Social media, Instagram, Facebook, Twitter, TikTok, Influencer, Blogger, YouTuber, Vlogger, Podcast, Streaming, Followers, Hashtag, Trend, Viral, Engagement, Comment, Like, Share, Notification, Mobile, Screen time, Productivity, Calendar, Planner, Notes, Reading, Book, Magazine, Newspaper, Writer, Author, Pen, Notebook, Study, School, Student, Teacher, Classroom, Lecture, Blackboard, Whiteboard, Homework, Exam, Graduation, Diploma, Backpack, University, Campus, Education, Learning, Research, Science, Chemistry, Biology, Physics, Lab, Experiment, Test tube, Microscope, DNA, Molecule, Planet, Space, Rocket, Astronaut, Satellite, Earth, Mars, Galaxy, Universe, Starscape, Space travel, Cosmonaut, Launch, Technology in space, Rocket launch, Nature and space, Environment, Sustainability, Eco, Recycling, Green energy, Solar panels, Wind turbines, Nature conservation, Wildlife, Forests, Ocean plastic, Pollution, Climate change, Earth Day, Clean up, Reuse, Zero waste, Minimalism, Simple living, Countryside, Farm, Agriculture, Farmer, Tractor, Barn, Field, Harvest, Sunrise over fields, Animals, Pets, Dog, Puppy, Cat, Kitten, Bird, Parrot, Fish, Hamster, Rabbit, Horse, Cow, Sheep, Goat, Chicken, Duck, Wildlife, Elephant, Lion, Tiger, Bear, Giraffe, Zebra, Monkey, Panda, Kangaroo, Deer, Fox, Wolf, Owl, Eagle, Dolphin, Whale, Shark, Penguin, Insects, Butterfly, Bee, Spider, Ant, Dragonfly, Nature life, Animal rescue, Animal shelter, Car, Cars, Sports car, Luxury car, Classic car, Vintage car, Electric car, Sedan, Coupe, SUV, Crossover, Hatchback, Convertible, Pickup truck, Off-road car, 4x4 car, Race car, Supercar, Hypercar, Muscle car, Modified car, Tuned car, JDM car, Euro car, American muscle, Drift car, Street car, Urban car, Car interior, Car dashboard, Steering wheel, Car engine, Car headlights, LED lights, Car exhaust, Car tires, Rims, Alloy wheels, Car wash, Car detailing, Car wrap, Car paint, Matte car, Glossy car, Black car, White car, Red car, Blue car, Yellow car, Green car, Orange car, Grey car, Silver car, Gold car, Night car, Sunset car, Road trip car, Parked car, Driving car, Highway car, City car, Mountain road car, Desert road car, Beach car, Convertible on beach, Luxury interior, Electric vehicle, Tesla, BMW, Mercedes-Benz, Audi, Porsche, Ferrari, Lamborghini, McLaren, Bugatti, Rolls-Royce, Bentley, Land Rover, Range Rover, Jeep, Toyota, Honda, Nissan, Mazda, Subaru, Mitsubishi, Ford, Chevrolet, Dodge, GMC, Cadillac, Chrysler, Lincoln, Hyundai, Kia, Peugeot, Renault, Citroën, Fiat, Alfa Romeo, Volvo, Opel, SEAT, Skoda, Volkswagen, Mini Cooper, Smart car, Tuning car, Car show, Car meet, Fast car, Rally car, Dakar car, Dakar rally, Police car, Taxi car, Uber car, Car on road, Car drifting, Car burnout, Burnout smoke, Car lights at night, Night drive, Rainy car, Car in snow, Car in forest, Car silhouette, Car speed, Motorcycle, Motorbike, Bike, Cruiser bike, Chopper bike, Touring bike, Dirt bike, Enduro bike, Sportbike, Naked bike, Adventure bike, Custom bike, Cafe racer, Scooter, Vespa, Retro bike, Vintage motorcycle, Electric motorcycle, Motorbike helmet, Motorcycle jacket, Motorbike gear, Motorcycle engine, Motorcycle exhaust, Motorcycle wheels, Biker, Motorcycle rider, Motorcycle gang, Motorcycle parking, Motorbike road trip, Motorbike highway, Bike on beach, Bike in forest, Urban motorcycle, Night rider, Motorcycle drifting, Motorbike race, MotoGP, Street bike, Highway ride, Two-wheeler, City bike, Motorbike lights, Motorcycle on hill, Motorbike adventure, Riding couple, Rider lifestyle, Biker culture, Motorcycle club, Motorbike posing, Motorcycle tour, Motorcycle sunset, Scooter ride, Vespa vintage, Classic motorbike, Old motorcycle, Fast motorcycle, Bike in motion, Off-road motorcycle, Dual sport bike, Wildlife, Safari, Animal kingdom, Nature reserve, National park, Rainforest, Jungle, Savannah, Wetlands, Coral reef, Wildlife sanctuary, Wildlife photography, Animal migration, Predator, Prey, Ecosystem, Biodiversity, Endangered species, Wildlife conservation, Natural habitat, Poaching, Wildlife rescue, Wildlife documentary, Animal behavior, Wildlife biologist, Wildlife tracker, Wildlife tour, Wildlife safari, Wild animals, Mammals, Reptiles, Amphibians, Birds, Insects, Marine life, Coral ecosystem, Oceanic life, Freshwater wildlife, Nocturnal animals, Herbivores, Carnivores, Omnivores, Apex predator, Keystone species, Wildlife corridor, Wildlife refuge, Wildlife habitat, Animal tracks, Camouflage, Animal adaptations, Wildlife protection, Animal sanctuary, Animal rescue, Wildlife NGO, Wildlife foundation, Wildlife monitoring, Wildlife research, Wildlife protection laws, Anti-poaching patrol, Forest ranger, Jungle expedition, Mountain wildlife, Arctic wildlife, Desert wildlife, Forest animals, River animals, Marine sanctuary, Birdwatching, Bird migration, Bird songs, Raptors, Songbirds, Waterfowl, Shorebirds, Wild cats, Lions, Tigers, Leopards, Cheetahs, Jaguars, Wolves, Foxes, Bears, Polar bears, Pandas, Elephants, Rhinos, Hippos, Giraffes, Zebras, Antelope, Bison, Deer, Moose, Elk, Kangaroos, Koalas, Sloths, Monkeys, Apes, Gorillas, Chimpanzees, Orangutans, Lemurs, Crocodiles, Alligators, Snakes, Vipers, Pythons, Boas, Lizards, Geckos, Chameleons, Turtles, Tortoises, Frogs, Toads, Salamanders, Newts, Butterflies, Moths, Bees, Wasps, Ants, Termites, Beetles, Ladybugs, Dragonflies, Fireflies, Spiders, Scorpions, Crabs, Lobsters, Octopuses, Jellyfish, Whales, Dolphins, Seals, Sea lions, Manatees, Sharks, Rays, Sea turtles, Coral species, Reef fish, Clownfish, Angelfish, Eels, Seagulls, Albatrosses, Penguins, Arctic fox, Snow leopard, Lynx, Wild boar, Hedgehogs, Porcupines, Skunks, Raccoons, Opossums, Badgers, Weasels, Martens, Meerkats, Mongooses, Hyenas, Warthogs, Aardvarks, Armadillos, Tapirs, Capybaras, Otters, Beavers, Platypus, Pangolins, Civets, Genets, Fennec fox, Jackals, Caracals, Servals, Fishing cats, Snowy owls, Eagles, Hawks, Falcons, Vultures, Kites, Owls, Hornbills, Toucans, Macaws, Parrots, Peacocks, Cranes, Flamingos, Storks, Herons, Ibis, Pelicans, Swans, Ducks, Geese, Loons, Puffins, Bats, Vampire bats, Flying foxes, Wild horses, Mustangs, Camels, Llamas, Alpacas, Yaks, Mountain goats, Ibex, Reindeer, Caribou, Musk ox, Snow hares, Arctic hares, Gophers, Prairie dogs, Groundhogs, Moles, Shrews, Voles, Field mice, Jungle fowl, Quail, Pheasants, Turkeys, Wild pigs, Wild turkeys, Wild chickens, Bush babies, Tree frogs, Poison dart frogs, Goliath frogs, Glass frogs, Jungle snakes, Desert lizards, Tree-dwelling animals, Arboreal wildlife, Burrowing animals, Nocturnal creatures, Diurnal animals, Migratory animals, Solitary animals, Pack animals, Herd animals, Wildlife patterns, Wildlife signs, Wildlife ecology, Wildlife balance, Wild nature, Untamed lands, Wilderness, Pristine ecosystem, Rewilding, Forest canopy, Understory animals, Wildlife cycles, Wildlife threats, Natural predators, Wildlife impact, Wildlife zones, Jungle canopy, Marshlands, Swamp life, Highland wildlife, Lowland species, Forest floor creatures, Wildlife encounters, Rare animals, Unique species, Elusive wildlife, Wild instincts, Wildlife coexistence, Animal tracks, Nature sounds, Animal calls, Wildlife trails, Animal dens, Nesting sites, Wildlife nests, Animal markings, Territorial animals, Mating season, Wildlife breeding, Wildlife education, Environmental awareness, Nature lovers, Wildlife explorers, Animal shelters, Animal tracking, Conservation efforts, Habitat loss, Ecosystem collapse, Wildlife wonders, Wild territories, Endemic species, Island wildlife, Wildlife richness, Ecotourism, Jungle tours, Marine exploration, Wildlife explorers, Wild discoveries, Natural exploration, Animal camouflage, Jungle survival, Animal instincts, Wild animal footprints, Animal sounds at night, Tropical forest life, Eiffel Tower, Grand Canyon, Great Wall of China, Machu Picchu, Mount Everest, Niagara Falls, Santorini, Taj Mahal, Times Square, Tokyo Tower, Mount Fuji, Bora Bora, Maldives, Statue of Liberty, Colosseum, Leaning Tower of Pisa, Sydney Opera House, Big Ben, Buckingham Palace, Louvre Museum, Yellowstone National Park, Banff National Park, Victoria Falls, Sahara Desert, Amazon Rainforest, Galápagos Islands, Petra, Angkor Wat, Burj Khalifa, Dead Sea, Blue Lagoon Iceland, Cappadocia, Sagrada Familia, Ha Long Bay, Mount Kilimanjaro, Serengeti, Ayers Rock, Zion National Park, Yosemite National Park, Bryce Canyon, Antelope Canyon, Amalfi Coast, Lake Como, Matterhorn, Jungfrau, Neuschwanstein Castle, Cliffs of Moher, Skellig Michael, Giant’s Causeway, Icelandic Highlands, Fjords of Norway, Lapland, Northern Lights, Swiss Alps, Dolomites, Cinque Terre, Bora Bora Lagoon, Seychelles, Phi Phi Islands, Halong Bay, Bali, Ubud, Komodo Island, Jeju Island, Nami Island, Mount Rinjani, Mount Bromo, Raja Ampat, Sapa, Da Nang, Hoi An, Hue Citadel, Angkor Thom, Bagan, Inle Lake, Golden Rock, Kuala Lumpur Tower, Marina Bay Sands, Gardens by the Bay, Merlion, Shibuya Crossing, Kyoto Temples, Osaka Castle, Nara Park, Mount Aso, Jeju Volcanic Island, Taipei 101, Sun Moon Lake, Taroko Gorge, Palawan, El Nido, Coron, Chocolate Hills, Banaue Rice Terraces, Boracay, Dubai Marina, Sheikh Zayed Mosque, Abu Dhabi Corniche, Alhambra, Barcelona Gothic Quarter, Mont Saint-Michel, Palace of Versailles, Loch Ness, Edinburgh Castle, St. Basil’s Cathedral, Red Square, Kremlin, Trans-Siberian Railway, Lake Baikal, Gobi Desert, Mount Elbrus, Iguazu Falls, Patagonia, Andes Mountains, Rio de Janeiro, Christ the Redeemer, Copacabana Beach, Amazon River, Galapagos Islands, Havana Old Town, Varadero Beach, Cancun, Chichen Itza, Tulum Ruins, Cenote Ik Kil, Yellowstone Geysers, Grand Prismatic Spring, Death Valley, Monument Valley, Arches National Park, Glacier National Park, Lake Louise, Moraine Lake, Jasper National Park, Rockies, Moeraki Boulders, Milford Sound, Mount Cook, Rotorua, Queenstown, Hobbiton, Great Ocean Road, Twelve Apostles, Uluru, Kakadu National Park, Fraser Island, Whitsunday Islands, Great Barrier Reef, Sydney Harbour, Bondi Beach, Perth Beaches, Tasmanian Wilderness, Iceland Glaciers, Vatnajokull, Skogafoss, Seljalandsfoss, Thingvellir, Golden Circle, Reykjavik, Trolltunga, Preikestolen, Geirangerfjord, Lofoten Islands, Arctic Circle, Lapland Reindeer, Icehotel, Northern Sweden, Finnish Lakes, Rovaniemi, Santa Claus Village 2 | -------------------------------------------------------------------------------- /download_random_image.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | from PIL import Image, ImageFilter, ImageEnhance 4 | from io import BytesIO 5 | from pathlib import Path 6 | import json 7 | import hashlib 8 | from datetime import datetime, timedelta 9 | import concurrent.futures 10 | import time 11 | import threading 12 | from colorama import init, Fore, Back, Style 13 | from tabulate import tabulate 14 | import random 15 | import psutil 16 | 17 | # Khởi tạo colorama 18 | init() 19 | 20 | class ImageDownloader: 21 | def __init__(self): 22 | self.api_key = "Ms9jiPvj8G1EviJwdgZGHd3Kztbo5fDbWWsEuHTujCa0SVL89oStHcqk" 23 | self.headers = {"Authorization": self.api_key} 24 | # API key cho TheCatAPI 25 | self.catapi_key = "live_kTZGJC9WjJwYQ12oGl2M95H8QOxEpV49KxO5sVy8QfHfRRkO0vVPLCQVoBaCLmPw" 26 | self.catapi_headers = {"x-api-key": self.catapi_key} 27 | self.downloaded_images = self.load_downloaded_images() 28 | self.downloaded_hashes = set() # Thêm thuộc tính downloaded_hashes 29 | self.debug_mode = False # Thêm thuộc tính debug_mode 30 | self.download_count = 0 # Thêm thuộc tính download_count 31 | self.total_downloaded = 0 32 | self.global_counter = self.get_last_counter() 33 | self.lock = threading.Lock() 34 | self.total_requested = 0 35 | self.total_successful = 0 36 | self.total_failed = 0 37 | self.start_time = None 38 | self.last_update_time = None 39 | self.last_successful_count = 0 40 | self.last_api_call = 0 41 | self.api_call_interval = 1.0 42 | self.rate_limit_reset = 0 43 | self.rate_limit_remaining = 0 44 | self.successful_by_source = {"pexels": 0, "thispersondoesnotexist": 0, "picsum": 0, "catapi": 0} 45 | self.download_speed = 1.0 46 | self.num_threads = 1 47 | self.thispersondoesnotexist_speed = 3.33 # 300ms = 3.33 ảnh/giây 48 | self.thispersondoesnotexist_threads = 10 # Số luồng mặc định cho thispersondoesnotexist 49 | self.picsum_speed = 3.33 # 300ms = 3.33 ảnh/giây 50 | self.picsum_threads = 10 # Số luồng mặc định cho picsum 51 | self.catapi_speed = 3.33 # 300ms = 3.33 ảnh/giây 52 | self.catapi_threads = 10 # Số luồng mặc định cho catapi 53 | self.apis_to_use = [] # Danh sách API được chọn 54 | self.cpu_threshold = 85 # Ngưỡng CPU cao (%) 55 | self.cpu_target = 60 # Mức CPU mục tiêu (%) 56 | self.adaptive_delay = 0.1 # Thời gian delay tự động (giây) 57 | self.adaptive_enabled = True # Bật/tắt chế độ điều chỉnh CPU tự động 58 | 59 | # Tạo thư mục output nếu chưa tồn tại 60 | output_dir = Path("output") 61 | output_dir.mkdir(exist_ok=True) 62 | 63 | def get_last_counter(self): 64 | """Lấy số thứ tự cuối cùng từ thư mục output""" 65 | output_dir = Path("output") 66 | if not output_dir.exists(): 67 | return 0 68 | 69 | max_counter = 0 70 | for file in output_dir.glob("IMG_*.jpg"): 71 | try: 72 | counter = int(file.stem.split('_')[1]) 73 | max_counter = max(max_counter, counter) 74 | except (ValueError, IndexError): 75 | continue 76 | return max_counter 77 | 78 | def load_downloaded_images(self): 79 | """Tải danh sách ảnh đã tải về từ file""" 80 | try: 81 | with open("downloaded_images.json", "r") as f: 82 | return json.load(f) 83 | except FileNotFoundError: 84 | return {} 85 | 86 | def save_downloaded_images(self): 87 | """Lưu danh sách ảnh đã tải về vào file""" 88 | with open("downloaded_images.json", "w") as f: 89 | json.dump(self.downloaded_images, f, indent=2) 90 | 91 | def calculate_image_hash(self, image): 92 | """Tính toán hash của ảnh để kiểm tra trùng lặp""" 93 | try: 94 | # Chuyển ảnh sang grayscale để dễ so sánh 95 | img_gray = image.convert('L') 96 | # Giảm kích thước để tăng tốc độ tính toán 97 | img_small = img_gray.resize((8, 8), Image.LANCZOS) 98 | # Lấy giá trị độ sáng của từng pixel 99 | pixels = list(img_small.getdata()) 100 | # Tính ngưỡng trung bình 101 | avg = sum(pixels) / len(pixels) 102 | # Tạo hash nhị phân: 1 nếu pixel sáng hơn trung bình, 0 nếu ngược lại 103 | bits = ''.join('1' if pixel > avg else '0' for pixel in pixels) 104 | # Chuyển chuỗi bit thành số nguyên 105 | hash_value = int(bits, 2) 106 | return hash_value 107 | except Exception as e: 108 | self.debug_print(f"Lỗi khi tính toán hash ảnh: {str(e)}") 109 | # Trả về giá trị ngẫu nhiên nếu có lỗi để tránh trùng lặp 110 | return random.randint(1, 10**10) 111 | 112 | def is_duplicate(self, image_hash): 113 | """Kiểm tra xem ảnh đã được tải về chưa""" 114 | with self.lock: 115 | return image_hash in self.downloaded_hashes 116 | 117 | def download_image(self, url): 118 | """Tải ảnh từ URL""" 119 | try: 120 | response = requests.get(url, timeout=10) 121 | if response.status_code == 200: 122 | image = Image.open(BytesIO(response.content)) 123 | return image 124 | except Exception as e: 125 | print(f"Lỗi khi tải ảnh: {str(e)}") 126 | return None 127 | 128 | def resize_image(self, image, target_width=360, target_height=640): 129 | """Thay đổi kích thước ảnh với chất lượng tốt hơn""" 130 | # Tính tỷ lệ khung hình gốc 131 | original_ratio = image.width / image.height 132 | target_ratio = target_width / target_height 133 | 134 | # Cắt ảnh để phù hợp với tỷ lệ khung hình mới 135 | if original_ratio > target_ratio: 136 | # Ảnh rộng hơn, cắt hai bên 137 | new_width = int(image.height * target_ratio) 138 | left = (image.width - new_width) // 2 139 | image = image.crop((left, 0, left + new_width, image.height)) 140 | else: 141 | # Ảnh cao hơn, cắt trên dưới 142 | new_height = int(image.width / target_ratio) 143 | top = (image.height - new_height) // 2 144 | image = image.crop((0, top, image.width, top + new_height)) 145 | 146 | # Thay đổi kích thước 147 | return image.resize((target_width, target_height), Image.LANCZOS) 148 | 149 | def optimize_image(self, image): 150 | """Tối ưu hóa ảnh trước khi lưu""" 151 | # Chuyển đổi sang RGB nếu là ảnh RGBA 152 | if image.mode in ('RGBA', 'LA'): 153 | background = Image.new('RGB', image.size, (255, 255, 255)) 154 | background.paste(image, mask=image.split()[-1]) 155 | image = background 156 | elif image.mode != 'RGB': 157 | image = image.convert('RGB') 158 | 159 | # Tăng độ tương phản và độ sáng 160 | enhancer = ImageEnhance.Contrast(image) 161 | image = enhancer.enhance(1.1) 162 | 163 | enhancer = ImageEnhance.Brightness(image) 164 | image = enhancer.enhance(1.05) 165 | 166 | # Áp dụng bộ lọc để cải thiện độ nét 167 | image = image.filter(ImageFilter.SHARPEN) 168 | 169 | return image 170 | 171 | def save_image(self, image_data, filename, source): 172 | """Lưu ảnh với kích thước tối đa là 10KB""" 173 | try: 174 | # Chuyển binary data thành đối tượng Image 175 | image = Image.open(BytesIO(image_data)) 176 | 177 | # Chuyển ảnh sang RGB nếu có kênh Alpha để tránh lỗi 178 | if image.mode in ('RGBA', 'LA') or (image.mode == 'P' and 'transparency' in image.info): 179 | background = Image.new('RGB', image.size, (255, 255, 255)) 180 | background.paste(image, mask=image.split()[3] if image.mode == 'RGBA' else None) 181 | image = background 182 | 183 | # Kiểm tra kích thước của ảnh và resize nếu cần 184 | width, height = image.size 185 | max_dimension = 800 186 | 187 | if width > max_dimension or height > max_dimension: 188 | if width > height: 189 | new_width = max_dimension 190 | new_height = int(height * (max_dimension / width)) 191 | else: 192 | new_height = max_dimension 193 | new_width = int(width * (max_dimension / height)) 194 | 195 | image = image.resize((new_width, new_height), Image.LANCZOS) 196 | self.debug_print(f"[{source}] Đã resize ảnh từ {width}x{height} xuống {new_width}x{new_height}") 197 | 198 | # Tính toán hash của ảnh để kiểm tra trùng lặp 199 | image_hash = self.calculate_image_hash(image) 200 | 201 | # Kiểm tra trùng lặp 202 | if self.is_duplicate(image_hash): 203 | self.debug_print(f"[{source}] Ảnh trùng lặp bỏ qua: {filename}") 204 | return False 205 | 206 | # Thêm hash vào danh sách đã tải 207 | with self.lock: 208 | self.downloaded_hashes.add(image_hash) 209 | 210 | # Đảm bảo thư mục tồn tại 211 | os.makedirs('images', exist_ok=True) 212 | 213 | # Binary search để tìm chất lượng tối ưu để ảnh dưới 10KB 214 | quality_low = 5 215 | quality_high = 95 216 | best_quality = 0 217 | best_image_data = None 218 | 219 | while quality_low <= quality_high: 220 | quality_mid = (quality_low + quality_high) // 2 221 | 222 | # Lưu ảnh tạm với chất lượng thử nghiệm 223 | temp_buffer = BytesIO() 224 | image.save(temp_buffer, format='JPEG', quality=quality_mid, optimize=True) 225 | temp_buffer.seek(0) 226 | current_size = len(temp_buffer.getvalue()) 227 | 228 | if current_size <= 10240: # 10KB = 10240 bytes 229 | best_quality = quality_mid 230 | best_image_data = temp_buffer.getvalue() 231 | quality_low = quality_mid + 1 232 | else: 233 | quality_high = quality_mid - 1 234 | 235 | if best_image_data: 236 | # Lưu ảnh với chất lượng tốt nhất đáp ứng yêu cầu kích thước 237 | file_path = os.path.join('images', f"{filename}.jpg") 238 | with open(file_path, 'wb') as f: 239 | f.write(best_image_data) 240 | 241 | self.debug_print(f"[{source}] Lưu ảnh thành công: {file_path} (Chất lượng: {best_quality}%, Kích thước: {len(best_image_data)/1024:.2f}KB)") 242 | return True 243 | else: 244 | self.debug_print(f"[{source}] Không thể lưu ảnh dưới 10KB: {filename}") 245 | return False 246 | 247 | except Exception as e: 248 | self.debug_print(f"[{source}] Lỗi khi lưu ảnh {filename}: {str(e)}") 249 | return False 250 | 251 | def update_progress(self): 252 | """Cập nhật và hiển thị bảng tiến độ""" 253 | os.system('cls' if os.name == 'nt' else 'clear') # Xóa màn hình 254 | 255 | # Tính thời gian ước tính còn lại 256 | current_time = time.time() 257 | if self.last_update_time and self.last_successful_count < self.total_successful: 258 | time_diff = current_time - self.last_update_time 259 | success_diff = self.total_successful - self.last_successful_count 260 | if success_diff > 0: 261 | time_per_image = time_diff / success_diff 262 | remaining_images = self.total_requested - self.total_successful 263 | estimated_seconds = remaining_images * time_per_image 264 | estimated_end = datetime.now() + timedelta(seconds=estimated_seconds) 265 | estimated_time = estimated_end.strftime("%H:%M:%S") 266 | else: 267 | estimated_time = "Đang tính toán..." 268 | else: 269 | estimated_time = "Đang tính toán..." 270 | 271 | self.last_update_time = current_time 272 | self.last_successful_count = self.total_successful 273 | 274 | # Bảng tiến độ chính 275 | main_table = [ 276 | ["Tổng số Ảnh yêu cầu", "Số ảnh đã tải được", "Số ảnh tải thất bại"], 277 | [ 278 | f"{Fore.GREEN}{self.total_requested}{Style.RESET_ALL}", 279 | f"{Fore.BLUE}{self.total_successful}{Style.RESET_ALL}", 280 | f"{Fore.RED}{self.total_failed}{Style.RESET_ALL}" 281 | ] 282 | ] 283 | print(tabulate(main_table, headers="firstrow", tablefmt="grid")) 284 | 285 | # Bảng thống kê theo nguồn 286 | source_table = [ 287 | ["Nguồn", "Số ảnh", "Tỷ lệ"] 288 | ] 289 | 290 | # Chỉ hiển thị các nguồn đang được sử dụng 291 | if "pexels" in self.apis_to_use: 292 | source_table.append([ 293 | f"{Fore.CYAN}Pexels{Style.RESET_ALL}", 294 | f"{self.successful_by_source['pexels']}", 295 | f"{(self.successful_by_source['pexels']/self.total_successful*100 if self.total_successful > 0 else 0):.1f}%" 296 | ]) 297 | 298 | if "thispersondoesnotexist" in self.apis_to_use: 299 | source_table.append([ 300 | f"{Fore.MAGENTA}ThisPersonDoesNotExist{Style.RESET_ALL}", 301 | f"{self.successful_by_source['thispersondoesnotexist']}", 302 | f"{(self.successful_by_source['thispersondoesnotexist']/self.total_successful*100 if self.total_successful > 0 else 0):.1f}%" 303 | ]) 304 | 305 | if "picsum" in self.apis_to_use: 306 | source_table.append([ 307 | f"{Fore.YELLOW}Picsum Photos{Style.RESET_ALL}", 308 | f"{self.successful_by_source['picsum']}", 309 | f"{(self.successful_by_source['picsum']/self.total_successful*100 if self.total_successful > 0 else 0):.1f}%" 310 | ]) 311 | 312 | if "catapi" in self.apis_to_use: 313 | source_table.append([ 314 | f"{Fore.GREEN}TheCatAPI{Style.RESET_ALL}", 315 | f"{self.successful_by_source['catapi']}", 316 | f"{(self.successful_by_source['catapi']/self.total_successful*100 if self.total_successful > 0 else 0):.1f}%" 317 | ]) 318 | 319 | print("\nThống kê theo nguồn:") 320 | print(tabulate(source_table, headers="firstrow", tablefmt="grid")) 321 | 322 | print(f"\nTiến độ: {(self.total_successful/self.total_requested)*100:.2f}%") 323 | print(f"Thời gian kết thúc ước tính: {Fore.YELLOW}{estimated_time}{Style.RESET_ALL}") 324 | 325 | def download_from_thispersondoesnotexist(self): 326 | """Tải ảnh từ thispersondoesnotexist.com""" 327 | try: 328 | headers = { 329 | 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 330 | 'accept-language': 'en-US,en;q=0.9,vi;q=0.8', 331 | 'cache-control': 'max-age=0', 332 | 'dnt': '1', 333 | 'priority': 'u=0, i', 334 | 'referer': 'https://www.google.com/', 335 | 'sec-ch-ua': '"Microsoft Edge";v="135", "Not-A.Brand";v="8", "Chromium";v="135"', 336 | 'sec-ch-ua-mobile': '?0', 337 | 'sec-ch-ua-platform': '"Windows"', 338 | 'sec-fetch-dest': 'document', 339 | 'sec-fetch-mode': 'navigate', 340 | 'sec-fetch-site': 'cross-site', 341 | 'sec-fetch-user': '?1', 342 | 'upgrade-insecure-requests': '1', 343 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0' 344 | } 345 | 346 | response = requests.get("https://thispersondoesnotexist.com/", 347 | headers=headers, 348 | timeout=10) 349 | 350 | if response.status_code == 200: 351 | # Tạo ảnh trực tiếp từ response content 352 | image = Image.open(BytesIO(response.content)) 353 | return image 354 | else: 355 | print(f"Lỗi khi tải ảnh từ thispersondoesnotexist: Status code {response.status_code}") 356 | except Exception as e: 357 | print(f"Lỗi khi tải ảnh từ thispersondoesnotexist: {str(e)}") 358 | return None 359 | 360 | def download_from_picsum(self): 361 | """Tải ảnh từ picsum.photos""" 362 | try: 363 | url = "https://picsum.photos/360/640" 364 | response = requests.get(url, timeout=10, allow_redirects=True) 365 | 366 | if response.status_code == 200: 367 | # Tạo ảnh trực tiếp từ response content 368 | image = Image.open(BytesIO(response.content)) 369 | return image 370 | else: 371 | if self.debug_mode: 372 | print(f"[Picsum] Lỗi khi tải ảnh: Status code {response.status_code}") 373 | except Exception as e: 374 | if self.debug_mode: 375 | print(f"[Picsum] Lỗi khi tải ảnh: {str(e)}") 376 | return None 377 | 378 | def download_from_catapi(self): 379 | """Tải ảnh từ TheCatAPI""" 380 | try: 381 | # Lấy ngẫu nhiên một ảnh mèo từ TheCatAPI 382 | url = "https://api.thecatapi.com/v1/images/search?size=med" 383 | 384 | if self.debug_mode: 385 | print(f"[CatAPI] Đang gửi yêu cầu API...") 386 | 387 | response = requests.get(url, headers=self.catapi_headers, timeout=10) 388 | 389 | if response.status_code == 200: 390 | data = response.json() 391 | if data and len(data) > 0: 392 | image_url = data[0]["url"] 393 | if self.debug_mode: 394 | print(f"[CatAPI] Tìm thấy ảnh: {image_url}") 395 | 396 | # Tải ảnh 397 | img_response = requests.get(image_url, timeout=10) 398 | if img_response.status_code == 200: 399 | image = Image.open(BytesIO(img_response.content)) 400 | return image 401 | else: 402 | if self.debug_mode: 403 | print(f"[CatAPI] Lỗi khi tải ảnh: Status code {img_response.status_code}") 404 | else: 405 | if self.debug_mode: 406 | print("[CatAPI] Không tìm thấy ảnh nào trong kết quả API") 407 | else: 408 | if self.debug_mode: 409 | print(f"[CatAPI] Lỗi khi gọi API: Status code {response.status_code}") 410 | print(f"Response: {response.text}") 411 | except Exception as e: 412 | if self.debug_mode: 413 | print(f"[CatAPI] Lỗi khi tải ảnh: {str(e)}") 414 | return None 415 | 416 | def process_single_image(self, source): 417 | """Xử lý việc tải và lưu một ảnh từ nguồn cụ thể.""" 418 | try: 419 | if source == "thispersondoesnotexist": 420 | url = "https://thispersondoesnotexist.com" 421 | self.debug_print(f"[ThisPersonDoesNotExist] Đang tải ảnh từ {url}") 422 | response = requests.get(url, timeout=10) 423 | if response.status_code == 200: 424 | timestamp = int(time.time() * 1000) 425 | filename = f"person_{timestamp}" 426 | with self.lock: 427 | self.total_successful += 1 428 | self.successful_by_source["thispersondoesnotexist"] += 1 429 | success = self.save_image(response.content, filename, source) 430 | if success: 431 | self.debug_print(f"[ThisPersonDoesNotExist] Đã tải thành công ảnh") 432 | self.update_progress() 433 | return success 434 | else: 435 | self.debug_print(f"[ThisPersonDoesNotExist] Lỗi khi tải ảnh: {response.status_code}") 436 | 437 | elif source == "picsum": 438 | width = 800 439 | height = 600 440 | timestamp = int(time.time() * 1000) 441 | url = f"https://picsum.photos/{width}/{height}?random={timestamp}" 442 | self.debug_print(f"[Picsum] Đang tải ảnh từ {url}") 443 | response = requests.get(url, timeout=10) 444 | if response.status_code == 200: 445 | filename = f"picsum_{timestamp}" 446 | with self.lock: 447 | self.total_successful += 1 448 | self.successful_by_source["picsum"] += 1 449 | success = self.save_image(response.content, filename, source) 450 | if success: 451 | self.debug_print(f"[Picsum] Đã tải thành công ảnh") 452 | self.update_progress() 453 | return success 454 | else: 455 | self.debug_print(f"[Picsum] Lỗi khi tải ảnh: {response.status_code}") 456 | 457 | elif source == "catapi": 458 | url = "https://api.thecatapi.com/v1/images/search" 459 | headers = {"x-api-key": self.catapi_key} 460 | self.debug_print(f"[TheCatAPI] Đang gửi yêu cầu đến {url}") 461 | response = requests.get(url, headers=headers, timeout=10) 462 | if response.status_code == 200: 463 | data = response.json() 464 | if data and len(data) > 0: 465 | img_url = data[0]["url"] 466 | self.debug_print(f"[TheCatAPI] Đang tải ảnh từ {img_url}") 467 | img_response = requests.get(img_url, timeout=10) 468 | if img_response.status_code == 200: 469 | timestamp = int(time.time() * 1000) 470 | filename = f"cat_{timestamp}" 471 | with self.lock: 472 | self.total_successful += 1 473 | self.successful_by_source["catapi"] += 1 474 | success = self.save_image(img_response.content, filename, source) 475 | if success: 476 | self.debug_print(f"[TheCatAPI] Đã tải thành công ảnh") 477 | self.update_progress() 478 | return success 479 | else: 480 | self.debug_print(f"[TheCatAPI] Lỗi khi tải ảnh: {img_response.status_code}") 481 | else: 482 | self.debug_print(f"[TheCatAPI] Không tìm thấy ảnh trong phản hồi API") 483 | else: 484 | self.debug_print(f"[TheCatAPI] Lỗi khi truy vấn API: {response.status_code}") 485 | 486 | else: 487 | self.debug_print(f"Nguồn không được hỗ trợ: {source}") 488 | 489 | with self.lock: 490 | self.total_failed += 1 491 | self.update_progress() 492 | 493 | except Exception as e: 494 | self.debug_print(f"[{source.capitalize()}] Lỗi khi xử lý ảnh: {str(e)}") 495 | with self.lock: 496 | self.total_failed += 1 497 | self.update_progress() 498 | 499 | return False 500 | 501 | def adjust_cpu_load(self): 502 | """Điều chỉnh tải CPU bằng cách thay đổi thời gian chờ""" 503 | if not self.adaptive_enabled: 504 | return 505 | 506 | try: 507 | cpu_percent = psutil.cpu_percent(interval=0.1) 508 | 509 | if cpu_percent > self.cpu_threshold: 510 | # Tăng thời gian chờ nếu CPU quá cao 511 | self.adaptive_delay += 0.05 512 | if self.debug_mode: 513 | print(f"\n[CPU] Đang cao: {cpu_percent:.1f}%, tăng delay lên {self.adaptive_delay:.2f}s") 514 | time.sleep(self.adaptive_delay) 515 | elif cpu_percent > self.cpu_target and self.adaptive_delay > 0.1: 516 | # Giữ delay hiện tại nếu CPU vẫn cao hơn mức mục tiêu 517 | if self.debug_mode and random.random() < 0.01: # Chỉ in log 1% thời gian để giảm tải 518 | print(f"\n[CPU] Vẫn cao: {cpu_percent:.1f}%, giữ delay ở {self.adaptive_delay:.2f}s") 519 | time.sleep(self.adaptive_delay) 520 | elif self.adaptive_delay > 0.1: 521 | # Giảm thời gian chờ dần dần nếu CPU ổn định 522 | self.adaptive_delay = max(0.1, self.adaptive_delay - 0.01) 523 | if self.debug_mode and random.random() < 0.01: 524 | print(f"\n[CPU] Ổn định: {cpu_percent:.1f}%, giảm delay xuống {self.adaptive_delay:.2f}s") 525 | except Exception: 526 | # Bỏ qua lỗi nếu có 527 | pass 528 | 529 | def wait_for_rate_limit(self): 530 | """Đợi cho đến khi có thể gọi API tiếp""" 531 | current_time = time.time() 532 | time_since_last_call = current_time - self.last_api_call 533 | 534 | if time_since_last_call < self.api_call_interval: 535 | sleep_time = self.api_call_interval - time_since_last_call 536 | time.sleep(sleep_time) 537 | 538 | if self.rate_limit_remaining <= 0 and self.rate_limit_reset > current_time: 539 | sleep_time = self.rate_limit_reset - current_time 540 | print(f"\nĐã hết giới hạn API. Đợi {sleep_time:.1f} giây...") 541 | time.sleep(sleep_time) 542 | 543 | self.last_api_call = time.time() 544 | 545 | def update_rate_limit(self, response): 546 | """Cập nhật thông tin rate limit từ response""" 547 | if 'X-RateLimit-Reset' in response.headers: 548 | self.rate_limit_reset = int(response.headers['X-RateLimit-Reset']) 549 | if 'X-RateLimit-Remaining' in response.headers: 550 | self.rate_limit_remaining = int(response.headers['X-RateLimit-Remaining']) 551 | 552 | def make_api_request(self, url, params): 553 | """Thực hiện yêu cầu API với rate limiting""" 554 | max_retries = 5 555 | retry_delay = 2 556 | 557 | for attempt in range(max_retries): 558 | try: 559 | self.wait_for_rate_limit() 560 | if self.debug_mode: 561 | print(f"\n[Pexels] Đang gửi yêu cầu API lần {attempt + 1}...") 562 | print(f"URL: {url}") 563 | print(f"Params: {params}") 564 | 565 | response = requests.get(url, headers=self.headers, params=params, timeout=10) 566 | self.update_rate_limit(response) 567 | 568 | if response.status_code == 200: 569 | if self.debug_mode: 570 | print(f"[Pexels] Yêu cầu API thành công!") 571 | return response 572 | elif response.status_code == 429: 573 | if 'Retry-After' in response.headers: 574 | retry_after = int(response.headers['Retry-After']) 575 | print(f"\n[Pexels] API đã bị giới hạn. Đợi {retry_after} giây...") 576 | time.sleep(retry_after) 577 | else: 578 | print(f"\n[Pexels] API đã bị giới hạn. Thử lại sau {retry_delay} giây...") 579 | time.sleep(retry_delay * (attempt + 1)) 580 | else: 581 | print(f"\n[Pexels] Lỗi API: {response.status_code}") 582 | print(f"Response: {response.text}") 583 | time.sleep(retry_delay) 584 | except Exception as e: 585 | print(f"\n[Pexels] Lỗi kết nối: {str(e)}") 586 | time.sleep(retry_delay) 587 | 588 | return None 589 | 590 | def download_and_process_images(self, query, count): 591 | """Tải và xử lý ảnh""" 592 | if not self.start_time: 593 | self.start_time = time.time() 594 | 595 | search_url = "https://api.pexels.com/v1/search" 596 | page = 1 597 | successful_downloads = 0 598 | max_retries = 3 599 | 600 | if self.debug_mode: 601 | print(f"\n[Pexels] Bắt đầu tải ảnh cho từ khóa '{query}'") 602 | print(f"Số lượng ảnh cần tải: {count}") 603 | 604 | while successful_downloads < count and max_retries > 0: 605 | params = { 606 | "query": query, 607 | "per_page": min(40, (count - successful_downloads) * 2), 608 | "page": page, 609 | "size": "large" 610 | } 611 | 612 | response = self.make_api_request(search_url, params) 613 | if not response: 614 | max_retries -= 1 615 | if max_retries > 0: 616 | print(f"\n[Pexels] Không thể kết nối API. Còn {max_retries} lần thử...") 617 | time.sleep(5) 618 | continue 619 | break 620 | 621 | try: 622 | data = response.json() 623 | if self.debug_mode: 624 | print(f"\n[Pexels] Đã nhận được {len(data.get('photos', []))} ảnh từ API") 625 | 626 | if data["photos"]: 627 | with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: 628 | futures = [] 629 | for photo in data["photos"]: 630 | if successful_downloads >= count: 631 | break 632 | futures.append(executor.submit(self.process_single_image, photo["source"])) 633 | 634 | for future in concurrent.futures.as_completed(futures): 635 | if successful_downloads >= count: 636 | break 637 | try: 638 | result = future.result() 639 | if result: 640 | successful_downloads += 1 641 | if self.debug_mode: 642 | print(f"[Pexels] Đã tải thành công ảnh {successful_downloads}/{count}") 643 | except Exception as e: 644 | print(f"[Pexels] Lỗi khi xử lý ảnh: {str(e)}") 645 | with self.lock: 646 | self.total_failed += 1 647 | self.update_progress() 648 | else: 649 | max_retries -= 1 650 | if max_retries > 0: 651 | print(f"\n[Pexels] Không tìm thấy ảnh phù hợp cho '{query}'. Còn {max_retries} lần thử...") 652 | time.sleep(2) 653 | continue 654 | break 655 | 656 | except Exception as e: 657 | max_retries -= 1 658 | if max_retries > 0: 659 | print(f"\n[Pexels] Lỗi xử lý dữ liệu: {str(e)}. Còn {max_retries} lần thử...") 660 | time.sleep(2) 661 | continue 662 | break 663 | 664 | page += 1 665 | if page > 100: 666 | break 667 | 668 | time.sleep(random.uniform(0.5, 1.5)) 669 | 670 | if successful_downloads < count: 671 | print(f"\n[Pexels] Không thể tải đủ {count} ảnh cho từ khóa '{query}'. Đã tải được {successful_downloads} ảnh.") 672 | 673 | self.save_downloaded_images() 674 | return successful_downloads 675 | 676 | def download_from_pexels(self, keywords, total_count): 677 | """Tải ảnh từ Pexels""" 678 | if "pexels" not in self.apis_to_use: 679 | return 680 | 681 | remaining_pexels = total_count 682 | keyword_index = 0 683 | 684 | while remaining_pexels > 0 and keyword_index < len(keywords): 685 | keyword = keywords[keyword_index] 686 | count = min(remaining_pexels, 100) # Tải tối đa 100 ảnh mỗi lần 687 | if self.debug_mode: 688 | print(f"\n[Pexels] Đang tải {count} ảnh cho từ khóa '{keyword}'...") 689 | try: 690 | successful = self.download_and_process_images(keyword, count) 691 | remaining_pexels -= successful 692 | 693 | if successful == 0: 694 | keyword_index += 1 # Chuyển sang từ khóa tiếp theo nếu không tải được ảnh nào 695 | if self.debug_mode: 696 | print(f"[Pexels] Không tải được ảnh nào cho từ khóa '{keyword}'. Chuyển sang từ khóa tiếp theo.") 697 | else: 698 | keyword_index = 0 # Quay lại từ khóa đầu tiên nếu tải thành công 699 | if self.debug_mode: 700 | print(f"[Pexels] Đã tải thành công {successful} ảnh cho từ khóa '{keyword}'") 701 | except Exception as e: 702 | print(f"[Pexels] Lỗi khi tải ảnh: {str(e)}") 703 | keyword_index += 1 # Chuyển sang từ khóa tiếp theo nếu có lỗi 704 | 705 | def debug_print(self, message): 706 | """In thông điệp gỡ lỗi nếu chế độ debug được bật""" 707 | if hasattr(self, 'debug_mode') and self.debug_mode: 708 | print(message) 709 | 710 | def read_keywords(): 711 | """Đọc từ khóa từ file keyword.txt""" 712 | try: 713 | with open("keyword.txt", "r", encoding="utf-8") as f: 714 | content = f.read().strip() 715 | keywords = [kw.strip() for kw in content.split(",")] 716 | return keywords 717 | except FileNotFoundError: 718 | print("Không tìm thấy file keyword.txt") 719 | return [] 720 | except Exception as e: 721 | print(f"Lỗi khi đọc file keyword.txt: {str(e)}") 722 | return [] 723 | 724 | def main(): 725 | print("\n===== CHƯƠNG TRÌNH TẢI ẢNH TỰ ĐỘNG =====\n") 726 | 727 | # Tạo các thư mục cần thiết 728 | os.makedirs('images', exist_ok=True) 729 | os.makedirs('output', exist_ok=True) 730 | 731 | # Kiểm tra thư viện psutil 732 | try: 733 | import psutil 734 | except ImportError: 735 | print("\nĐang cài đặt thư viện psutil để giám sát CPU...") 736 | try: 737 | import subprocess 738 | subprocess.check_call(["pip", "install", "psutil"]) 739 | print("Đã cài đặt psutil thành công!") 740 | import psutil 741 | except Exception as e: 742 | print(f"Không thể cài đặt psutil: {str(e)}") 743 | print("Chương trình vẫn sẽ chạy nhưng không thể tối ưu hóa CPU.") 744 | 745 | # Hỏi người dùng có muốn bật chế độ debug không 746 | debug_mode = input("Bạn có muốn bật chế độ debug không? (y/n): ").lower() == 'y' 747 | 748 | if debug_mode: 749 | print("\n=== CHẾ ĐỘ DEBUG ĐÃ ĐƯỢC BẬT ===") 750 | print("Các thông tin chi tiết sẽ được hiển thị") 751 | print("="*30 + "\n") 752 | 753 | # Khởi tạo đối tượng downloader 754 | downloader = ImageDownloader() 755 | downloader.debug_mode = debug_mode 756 | 757 | # Hỏi về chế độ điều chỉnh CPU tự động 758 | auto_cpu = input("\nBạn có muốn bật chế độ điều chỉnh CPU tự động không? (y/n): ").lower() == 'y' 759 | downloader.adaptive_enabled = auto_cpu 760 | 761 | if auto_cpu: 762 | # Hỏi về ngưỡng CPU tối đa 763 | try: 764 | cpu_threshold = float(input("\nNhập ngưỡng CPU tối đa (%) (70-95): ")) 765 | if 70 <= cpu_threshold <= 95: 766 | downloader.cpu_threshold = cpu_threshold 767 | else: 768 | print("Giá trị không hợp lệ. Sử dụng giá trị mặc định 85%") 769 | except ValueError: 770 | print("Giá trị không hợp lệ. Sử dụng giá trị mặc định 85%") 771 | 772 | # Hỏi về mức CPU mục tiêu 773 | try: 774 | cpu_target = float(input("\nNhập mức CPU mục tiêu (%) (40-70): ")) 775 | if 40 <= cpu_target <= 70: 776 | downloader.cpu_target = cpu_target 777 | else: 778 | print("Giá trị không hợp lệ. Sử dụng giá trị mặc định 60%") 779 | except ValueError: 780 | print("Giá trị không hợp lệ. Sử dụng giá trị mặc định 60%") 781 | 782 | # Hỏi người dùng muốn sử dụng API nào 783 | print("\nCác API có sẵn:") 784 | print("1. Pexels (Có giới hạn API)") 785 | print("2. ThisPersonDoesNotExist (Không giới hạn)") 786 | print("3. Picsum Photos (Không giới hạn)") 787 | print("4. TheCatAPI (Ảnh mèo, có giới hạn API)") 788 | 789 | apis_to_use = [] 790 | while True: 791 | api_choice = input("\nChọn các API muốn sử dụng (VD: 1,2,3,4 hoặc 2,3,4): ") 792 | choices = [choice.strip() for choice in api_choice.split(',')] 793 | 794 | valid_choices = True 795 | for choice in choices: 796 | if choice not in ['1', '2', '3', '4']: 797 | valid_choices = False 798 | print("Lựa chọn không hợp lệ. Vui lòng chọn 1, 2, 3 hoặc 4.") 799 | break 800 | 801 | if valid_choices: 802 | if '1' in choices: 803 | apis_to_use.append("pexels") 804 | if '2' in choices: 805 | apis_to_use.append("thispersondoesnotexist") 806 | if '3' in choices: 807 | apis_to_use.append("picsum") 808 | if '4' in choices: 809 | apis_to_use.append("catapi") 810 | 811 | if apis_to_use: 812 | break 813 | else: 814 | print("Vui lòng chọn ít nhất một API.") 815 | 816 | downloader.apis_to_use = apis_to_use 817 | 818 | # Cài đặt cho Pexels nếu được chọn 819 | if "pexels" in apis_to_use: 820 | # Hỏi người dùng về tốc độ tải cho Pexels 821 | while True: 822 | try: 823 | download_speed = float(input("\nNhập tốc độ tải cho Pexels (ảnh/giây, ví dụ: 1.0): ")) 824 | if download_speed > 0: 825 | break 826 | print("Tốc độ phải lớn hơn 0") 827 | except ValueError: 828 | print("Vui lòng nhập số") 829 | 830 | # Hỏi người dùng về số luồng tải cho Pexels 831 | while True: 832 | try: 833 | num_threads = int(input("Nhập số luồng tải cho Pexels (1-10): ")) 834 | if 1 <= num_threads <= 10: 835 | break 836 | print("Số luồng phải nằm trong khoảng 1-10") 837 | except ValueError: 838 | print("Vui lòng nhập số") 839 | 840 | downloader.download_speed = download_speed 841 | downloader.num_threads = num_threads 842 | 843 | # Cài đặt cho ThisPersonDoesNotExist nếu được chọn 844 | if "thispersondoesnotexist" in apis_to_use: 845 | # Hỏi người dùng về tốc độ tải cho ThisPersonDoesNotExist 846 | while True: 847 | try: 848 | thispersondoesnotexist_speed = float(input("\nNhập tốc độ tải cho ThisPersonDoesNotExist (ảnh/giây, ví dụ: 3.33 = 300ms): ")) 849 | if thispersondoesnotexist_speed > 0: 850 | break 851 | print("Tốc độ phải lớn hơn 0") 852 | except ValueError: 853 | print("Vui lòng nhập số") 854 | 855 | # Hỏi người dùng về số luồng tải cho ThisPersonDoesNotExist 856 | while True: 857 | try: 858 | thispersondoesnotexist_threads = int(input("Nhập số luồng tải cho ThisPersonDoesNotExist (1-20): ")) 859 | if 1 <= thispersondoesnotexist_threads <= 20: 860 | break 861 | print("Số luồng phải nằm trong khoảng 1-20") 862 | except ValueError: 863 | print("Vui lòng nhập số") 864 | 865 | downloader.thispersondoesnotexist_speed = thispersondoesnotexist_speed 866 | downloader.thispersondoesnotexist_threads = thispersondoesnotexist_threads 867 | 868 | # Cài đặt cho Picsum nếu được chọn 869 | if "picsum" in apis_to_use: 870 | # Hỏi người dùng về tốc độ tải cho Picsum 871 | while True: 872 | try: 873 | picsum_speed = float(input("\nNhập tốc độ tải cho Picsum Photos (ảnh/giây, ví dụ: 3.33 = 300ms): ")) 874 | if picsum_speed > 0: 875 | break 876 | print("Tốc độ phải lớn hơn 0") 877 | except ValueError: 878 | print("Vui lòng nhập số") 879 | 880 | # Hỏi người dùng về số luồng tải cho Picsum 881 | while True: 882 | try: 883 | picsum_threads = int(input("Nhập số luồng tải cho Picsum Photos (1-20): ")) 884 | if 1 <= picsum_threads <= 20: 885 | break 886 | print("Số luồng phải nằm trong khoảng 1-20") 887 | except ValueError: 888 | print("Vui lòng nhập số") 889 | 890 | downloader.picsum_speed = picsum_speed 891 | downloader.picsum_threads = picsum_threads 892 | 893 | # Cài đặt cho TheCatAPI nếu được chọn 894 | if "catapi" in apis_to_use: 895 | # Hỏi người dùng về tốc độ tải cho TheCatAPI 896 | while True: 897 | try: 898 | catapi_speed = float(input("\nNhập tốc độ tải cho TheCatAPI (ảnh/giây, ví dụ: 3.33 = 300ms): ")) 899 | if catapi_speed > 0: 900 | break 901 | print("Tốc độ phải lớn hơn 0") 902 | except ValueError: 903 | print("Vui lòng nhập số") 904 | 905 | # Hỏi người dùng về số luồng tải cho TheCatAPI 906 | while True: 907 | try: 908 | catapi_threads = int(input("Nhập số luồng tải cho TheCatAPI (1-20): ")) 909 | if 1 <= catapi_threads <= 20: 910 | break 911 | print("Số luồng phải nằm trong khoảng 1-20") 912 | except ValueError: 913 | print("Vui lòng nhập số") 914 | 915 | # Nhập API key TheCatAPI tùy chọn 916 | custom_key = input("\nNhập API key của TheCatAPI (để trống để sử dụng key mặc định): ").strip() 917 | if custom_key: 918 | downloader.catapi_key = custom_key 919 | downloader.catapi_headers = {"x-api-key": custom_key} 920 | print("Đã cập nhật API key cho TheCatAPI") 921 | 922 | downloader.catapi_speed = catapi_speed 923 | downloader.catapi_threads = catapi_threads 924 | 925 | # Hỏi người dùng tổng số lượng ảnh muốn tải 926 | while True: 927 | try: 928 | total_count = int(input("\nNhập tổng số lượng ảnh muốn tải (ví dụ: 100000): ")) 929 | if total_count > 0: 930 | break 931 | print("Số lượng ảnh phải lớn hơn 0") 932 | except ValueError: 933 | print("Vui lòng nhập số") 934 | 935 | # Đọc từ khóa từ file (chỉ cần nếu sử dụng Pexels) 936 | keywords = [] 937 | if "pexels" in apis_to_use: 938 | if debug_mode: 939 | print("\nĐang đọc từ khóa từ file keyword.txt...") 940 | keywords = read_keywords() 941 | if not keywords: 942 | print("Không có từ khóa nào để tải ảnh. Vui lòng kiểm tra file keyword.txt") 943 | return 944 | if debug_mode: 945 | print(f"Đã đọc được {len(keywords)} từ khóa: {', '.join(keywords)}") 946 | 947 | # Cập nhật tổng số ảnh yêu cầu 948 | downloader.total_requested = total_count 949 | downloader.start_time = time.time() 950 | 951 | # Phân phối ảnh đều cho tất cả nguồn đã chọn 952 | counts = {} 953 | api_count = len(apis_to_use) 954 | per_api_count = total_count // api_count 955 | remainder = total_count % api_count 956 | 957 | for i, api in enumerate(apis_to_use): 958 | counts[api] = per_api_count 959 | if i < remainder: 960 | counts[api] += 1 961 | 962 | percents = {} 963 | for api in apis_to_use: 964 | percents[api] = int(counts[api] / total_count * 100) 965 | 966 | if debug_mode: 967 | print(f"\nSố lượng ảnh sẽ tải:") 968 | for api in apis_to_use: 969 | print(f"- Từ {api}: {counts[api]} ảnh ({percents[api]}%)") 970 | 971 | print(f"\nCài đặt tốc độ tải:") 972 | if "pexels" in apis_to_use: 973 | print(f"- Pexels: {downloader.download_speed} ảnh/giây, {downloader.num_threads} luồng") 974 | if "thispersondoesnotexist" in apis_to_use: 975 | print(f"- ThisPersonDoesNotExist: {downloader.thispersondoesnotexist_speed} ảnh/giây ({1000/downloader.thispersondoesnotexist_speed:.0f}ms), {downloader.thispersondoesnotexist_threads} luồng") 976 | if "picsum" in apis_to_use: 977 | print(f"- Picsum Photos: {downloader.picsum_speed} ảnh/giây ({1000/downloader.picsum_speed:.0f}ms), {downloader.picsum_threads} luồng") 978 | if "catapi" in apis_to_use: 979 | print(f"- TheCatAPI: {downloader.catapi_speed} ảnh/giây ({1000/downloader.catapi_speed:.0f}ms), {downloader.catapi_threads} luồng") 980 | 981 | # Hiển thị tiến độ ban đầu 982 | downloader.update_progress() 983 | 984 | # Tải song song từ tất cả các nguồn được chọn 985 | if debug_mode: 986 | print("\nBắt đầu tải song song từ các nguồn đã chọn...") 987 | if downloader.adaptive_enabled: 988 | print(f"Chế độ điều chỉnh CPU tự động: Đã bật") 989 | print(f"Ngưỡng CPU tối đa: {downloader.cpu_threshold}%") 990 | print(f"Mức CPU mục tiêu: {downloader.cpu_target}%") 991 | else: 992 | print(f"Chế độ điều chỉnh CPU tự động: Đã tắt") 993 | 994 | with concurrent.futures.ThreadPoolExecutor(max_workers=len(apis_to_use)) as executor: 995 | futures = [] 996 | 997 | if "pexels" in apis_to_use and counts["pexels"] > 0: 998 | futures.append(executor.submit(downloader.download_from_pexels, keywords, counts["pexels"])) 999 | 1000 | if "thispersondoesnotexist" in apis_to_use and counts["thispersondoesnotexist"] > 0: 1001 | # Tạo hàm cục bộ để tải ảnh từ ThisPersonDoesNotExist 1002 | def download_from_thispersondoesnotexist_wrapper(): 1003 | remaining = counts["thispersondoesnotexist"] 1004 | with concurrent.futures.ThreadPoolExecutor(max_workers=downloader.thispersondoesnotexist_threads) as ex: 1005 | futures_local = set() 1006 | batch_size = min(50, remaining) 1007 | 1008 | # Tạo lô tác vụ đầu tiên 1009 | for _ in range(batch_size): 1010 | if remaining <= 0: 1011 | break 1012 | futures_local.add(ex.submit(downloader.process_single_image, source="thispersondoesnotexist")) 1013 | remaining -= 1 1014 | 1015 | # Xử lý và tạo thêm tác vụ khi cần 1016 | while futures_local and remaining > 0: 1017 | done, futures_local = concurrent.futures.wait( 1018 | futures_local, 1019 | return_when=concurrent.futures.FIRST_COMPLETED 1020 | ) 1021 | 1022 | # Thêm tác vụ mới vào hàng đợi 1023 | new_batch = min(len(done), remaining) 1024 | for _ in range(new_batch): 1025 | futures_local.add(ex.submit(downloader.process_single_image, source="thispersondoesnotexist")) 1026 | remaining -= 1 1027 | 1028 | # Áp dụng tốc độ tải 1029 | if not downloader.adaptive_enabled: 1030 | time.sleep(1/downloader.thispersondoesnotexist_speed) 1031 | 1032 | futures.append(executor.submit(download_from_thispersondoesnotexist_wrapper)) 1033 | 1034 | if "picsum" in apis_to_use and counts["picsum"] > 0: 1035 | # Tạo hàm cục bộ để tải ảnh từ Picsum 1036 | def download_from_picsum_wrapper(): 1037 | remaining = counts["picsum"] 1038 | with concurrent.futures.ThreadPoolExecutor(max_workers=downloader.picsum_threads) as ex: 1039 | futures_local = set() 1040 | batch_size = min(50, remaining) 1041 | 1042 | # Tạo lô tác vụ đầu tiên 1043 | for _ in range(batch_size): 1044 | if remaining <= 0: 1045 | break 1046 | futures_local.add(ex.submit(downloader.process_single_image, source="picsum")) 1047 | remaining -= 1 1048 | 1049 | # Xử lý và tạo thêm tác vụ khi cần 1050 | while futures_local and remaining > 0: 1051 | done, futures_local = concurrent.futures.wait( 1052 | futures_local, 1053 | return_when=concurrent.futures.FIRST_COMPLETED 1054 | ) 1055 | 1056 | # Thêm tác vụ mới vào hàng đợi 1057 | new_batch = min(len(done), remaining) 1058 | for _ in range(new_batch): 1059 | futures_local.add(ex.submit(downloader.process_single_image, source="picsum")) 1060 | remaining -= 1 1061 | 1062 | # Áp dụng tốc độ tải 1063 | if not downloader.adaptive_enabled: 1064 | time.sleep(1/downloader.picsum_speed) 1065 | 1066 | futures.append(executor.submit(download_from_picsum_wrapper)) 1067 | 1068 | if "catapi" in apis_to_use and counts["catapi"] > 0: 1069 | # Tạo hàm cục bộ để tải ảnh từ TheCatAPI 1070 | def download_from_catapi_wrapper(): 1071 | remaining = counts["catapi"] 1072 | with concurrent.futures.ThreadPoolExecutor(max_workers=downloader.catapi_threads) as ex: 1073 | futures_local = set() 1074 | batch_size = min(50, remaining) 1075 | 1076 | # Tạo lô tác vụ đầu tiên 1077 | for _ in range(batch_size): 1078 | if remaining <= 0: 1079 | break 1080 | futures_local.add(ex.submit(downloader.process_single_image, source="catapi")) 1081 | remaining -= 1 1082 | 1083 | # Xử lý và tạo thêm tác vụ khi cần 1084 | while futures_local and remaining > 0: 1085 | done, futures_local = concurrent.futures.wait( 1086 | futures_local, 1087 | return_when=concurrent.futures.FIRST_COMPLETED 1088 | ) 1089 | 1090 | # Thêm tác vụ mới vào hàng đợi 1091 | new_batch = min(len(done), remaining) 1092 | for _ in range(new_batch): 1093 | futures_local.add(ex.submit(downloader.process_single_image, source="catapi")) 1094 | remaining -= 1 1095 | 1096 | # Áp dụng tốc độ tải 1097 | if not downloader.adaptive_enabled: 1098 | time.sleep(1/downloader.catapi_speed) 1099 | 1100 | futures.append(executor.submit(download_from_catapi_wrapper)) 1101 | 1102 | # Đợi tất cả các quá trình hoàn thành 1103 | concurrent.futures.wait(futures) 1104 | 1105 | # Hiển thị kết quả cuối cùng 1106 | print("\n" + "="*50) 1107 | print(f"Tổng kết:") 1108 | print(f"Tổng số ảnh yêu cầu: {Fore.GREEN}{total_count}{Style.RESET_ALL}") 1109 | print(f"Số ảnh đã tải thành công: {Fore.BLUE}{downloader.total_successful}{Style.RESET_ALL}") 1110 | print(f"Số ảnh tải thất bại: {Fore.RED}{downloader.total_failed}{Style.RESET_ALL}") 1111 | print(f"Tỷ lệ thành công: {Fore.CYAN}{(downloader.total_successful/total_count)*100:.2f}%{Style.RESET_ALL}") 1112 | print(f"Thời gian thực hiện: {Fore.YELLOW}{timedelta(seconds=int(time.time() - downloader.start_time))}{Style.RESET_ALL}") 1113 | print("="*50) 1114 | 1115 | if __name__ == "__main__": 1116 | main() 1117 | --------------------------------------------------------------------------------