CAM4Recorder icon indicating copy to clipboard operation
CAM4Recorder copied to clipboard

broken ;o(

Open testdev123456 opened this issue 3 years ago • 21 comments

sorry to bother, but few days ago the script stopped working since the website changed desing. I believe it may have to do with not finding "videoPlayUrl" or "videoAppUrl" strings so the hls link cannot be parsed like before (I know they haven't changed the hls url at all)

Also, github shows your script was updated 2 days ago! and... I CANNOT SEE CHANGES ! I'm so confused.

testdev123456 avatar Apr 28 '21 15:04 testdev123456

same here, since they changed Webpage Design the script doesnt work anymore.

sixerstyle avatar Apr 29 '21 06:04 sixerstyle

I changed a bit the script and updated to work with newly updated cam4

Click to expand!
import time
import datetime
import os
import threading
import sys
import configparser
import subprocess
import queue
import requests
import streamlink

if os.name == 'nt':
    import ctypes
    kernel32 = ctypes.windll.kernel32
    kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)

mainDir = sys.path[0]
Config = configparser.ConfigParser()
setting = {}

recording = []

hilos = []

def cls():
    os.system('cls' if os.name == 'nt' else 'clear')
    
def readConfig():
    global setting

    Config.read(mainDir + '/config.conf')
    setting = {
        'save_directory': Config.get('paths', 'save_directory'),
        'wishlist': Config.get('paths', 'wishlist'),
        'interval': int(Config.get('settings', 'checkInterval')),
        'postProcessingCommand': Config.get('settings', 'postProcessingCommand'),
        }
    try:
        setting['postProcessingThreads'] = int(Config.get('settings', 'postProcessingThreads'))
    except ValueError:
        if setting['postProcessingCommand'] and not setting['postProcessingThreads']:
            setting['postProcessingThreads'] = 1
    
    if not os.path.exists(f'{setting["save_directory"]}'):
        os.makedirs(f'{setting["save_directory"]}')

def postProcess():
    while True:
        while processingQueue.empty():
            time.sleep(1)
        parameters = processingQueue.get()
        model = parameters['model']
        path = parameters['path']
        filename = os.path.split(path)[-1]
        directory = os.path.dirname(path)
        file = os.path.splitext(filename)[0]
        subprocess.call(setting['postProcessingCommand'].split() + [path, filename, directory, model,  file, 'cam4'])

class Modelo(threading.Thread):
    def __init__(self, modelo):
        super().__init__()
        self.modelo = modelo
        self._stopevent = threading.Event()
        self.file = None
        self.online = None
        self.lock = threading.Lock()

    def run(self):
        global recording, hilos
        isOnline = self.isOnline()
        if isOnline == False:
            self.online = False
        else:
            self.online = True
            self.file = os.path.join(setting['save_directory'], self.modelo, f'{datetime.datetime.fromtimestamp(time.time()).strftime("%Y.%m.%d_%H.%M.%S")}_{self.modelo}.mp4')
            try:
                session = streamlink.Streamlink()
                streams = session.streams(f'hlsvariant://{isOnline}')
                stream = streams['best']
                fd = stream.open()
                if not isModelInListofObjects(self.modelo, recording):
                    os.makedirs(os.path.join(setting['save_directory'], self.modelo), exist_ok=True)
                    with open(self.file, 'wb') as f:
                        self.lock.acquire()
                        recording.append(self)
                        for index, hilo in enumerate(hilos):
                            if hilo.modelo == self.modelo:
                                del hilos[index]
                                break
                        self.lock.release()
                        while not (self._stopevent.isSet() or os.fstat(f.fileno()).st_nlink == 0):
                            try:
                                data = fd.read(1024)
                                f.write(data)
                            except:
                                fd.close()
                                break
                    if setting['postProcessingCommand']:
                            processingQueue.put({'model': self.modelo, 'path': self.file})
            except Exception as e:
                with open('log.log', 'a+') as f:
                    f.write(f'\n{datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S")} EXCEPTION: {e}\n')
                self.stop()
            finally:
                self.exceptionHandler()

    def exceptionHandler(self):
        self.stop()
        self.online = False
        self.lock.acquire()
        for index, hilo in enumerate(recording):
            if hilo.modelo == self.modelo:
                del recording[index]
                break
        self.lock.release()
        try:
            file = os.path.join(os.getcwd(), self.file)
            if os.path.isfile(file):
                if os.path.getsize(file) <= 1024:
                    os.remove(file)
        except Exception as e:
            with open('log.log', 'a+') as f:
                f.write(f'\n{datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S")} EXCEPTION: {e}\n')

    def isOnline(self):
        try:
            resp = requests.get(f'https://www.cam4.com/rest/v1.0/profile/{self.modelo}/streamInfo')
            hls_url = ''
            if 'cdnURL' in resp.json():
                hls_url = resp.json()['cdnURL']
            if len(hls_url):
                return hls_url
            else:
                return False
        except:
            return False

    def stop(self):
        self._stopevent.set()

class CleaningThread(threading.Thread):
    def __init__(self):
        super().__init__()
        self.interval = 0
        self.lock = threading.Lock()
        
    def run(self):
        global hilos, recording
        while True:
            self.lock.acquire()
            new_hilos = []
            for hilo in hilos:
                if hilo.is_alive() or hilo.online:
                    new_hilos.append(hilo)
            hilos = new_hilos
            self.lock.release()
            for i in range(10, 0, -1):
                self.interval = i
                time.sleep(1)

class AddModelsThread(threading.Thread):
    def __init__(self):
        super().__init__()
        self.wanted = []
        self.lock = threading.Lock()
        self.repeatedModels = []
        self.counterModel = 0

    def run(self):
        global hilos, recording
        lines = open(setting['wishlist'], 'r').read().splitlines()
        self.wanted = (x for x in lines if x)
        self.lock.acquire()
        aux = []
        for model in self.wanted:
            model = model.lower()
            if model in aux:
                self.repeatedModels.append(model)
            else:
                aux.append(model)
                self.counterModel = self.counterModel + 1
                if not isModelInListofObjects(model, hilos) and not isModelInListofObjects(model, recording):
                    thread = Modelo(model)
                    thread.start()
                    hilos.append(thread)
        for hilo in recording:
            if hilo.modelo not in aux:
                hilo.stop()
        self.lock.release()

def isModelInListofObjects(obj, lista):
    result = False
    for i in lista:
        if i.modelo == obj:
            result = True
            break
    return result

if __name__ == '__main__':
    readConfig()
    if setting['postProcessingCommand']:
        processingQueue = queue.Queue()
        postprocessingWorkers = []
        for i in range(0, setting['postProcessingThreads']):
            t = threading.Thread(target=postProcess)
            postprocessingWorkers.append(t)
            t.start()
    cleaningThread = CleaningThread()
    cleaningThread.start()
    while True:
        try:
            readConfig()
            addModelsThread = AddModelsThread()
            addModelsThread.start()
            i = 1
            for i in range(setting['interval'], 0, -1):
                cls()
                if len(addModelsThread.repeatedModels): print('The following models are more than once in wanted: [\'' + ', '.join(modelo for modelo in addModelsThread.repeatedModels) + '\']')
                print(f'{len(hilos):02d} alive Threads (1 Thread per non-recording model), cleaning dead/not-online Threads in {cleaningThread.interval:02d} seconds, {addModelsThread.counterModel:02d} models in wanted')
                print(f'Online Threads (models): {len(recording):02d}')
                print('The following models are being recorded:')
                for hiloModelo in recording: print(f'  Model: {hiloModelo.modelo}  -->  File: {os.path.basename(hiloModelo.file)}')
                print(f'Next check in {i:02d} seconds\r', end='')
                time.sleep(1)
            addModelsThread.join()
            del addModelsThread, i
        except:
            break

ahsand97 avatar May 14 '21 23:05 ahsand97

I changed a bit the script and updated to work with newly updated cam4

worked for me. thank you for taking the time to share it with us !!

testdev123456 avatar May 15 '21 23:05 testdev123456

@testdev123456 I'm working on a modification https://github.com/horacio9a/cam4-anonymous in the meantime you can use adapted @ahsand97 which works perfectly now with Streamlink instead of the outdated Livestream. Look at https://github.com/horacio9a/CAM4Recorder

horacio9a avatar May 20 '21 06:05 horacio9a

Thanks for your solution, @ahsand97 It works perfectly!

@ahsand97 @horacio9a @beaston02 Do you have a fix for beaston02's CB recorder? It suddenly stopped working this week :(

mabel69 avatar Jul 18 '21 00:07 mabel69

Thanks for your solution, @ahsand97 It works perfectly!

@ahsand97 @horacio9a @beaston02 Do you have a fix for beaston02's CB recorder? It suddenly stopped working this week :(

Yes, basically the same script but using CB's links, the only things needed are like if you were using the C4 recorder (having a config.conf file with whishlist, save_directory inside). There was a section gender used previously in the conf file but is pretty irrelevant, only model names in whishlist are needed.

Click to expand!
import time
import datetime
import os
import threading
import sys
import configparser
import streamlink
import subprocess
import queue
import requests

if os.name == 'nt':
    import ctypes
    kernel32 = ctypes.windll.kernel32
    kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)

mainDir = sys.path[0]
Config = configparser.ConfigParser()
setting = {}

recording = []

hilos = []

def cls():
    os.system('cls' if os.name == 'nt' else 'clear')
    
def readConfig():
    global setting

    Config.read(mainDir + '/config.conf')
    setting = {
        'save_directory': Config.get('paths', 'save_directory'),
        'wishlist': Config.get('paths', 'wishlist'),
        'interval': int(Config.get('settings', 'checkInterval')),
        'postProcessingCommand': Config.get('settings', 'postProcessingCommand'),
        }
    try:
        setting['postProcessingThreads'] = int(Config.get('settings', 'postProcessingThreads'))
    except ValueError:
        if setting['postProcessingCommand'] and not setting['postProcessingThreads']:
            setting['postProcessingThreads'] = 1
    
    if not os.path.exists(f'{setting["save_directory"]}'):
        os.makedirs(f'{setting["save_directory"]}')

def postProcess():
    while True:
        while processingQueue.empty():
            time.sleep(1)
        parameters = processingQueue.get()
        model = parameters['model']
        path = parameters['path']
        filename = os.path.split(path)[-1]
        directory = os.path.dirname(path)
        file = os.path.splitext(filename)[0]
        subprocess.call(setting['postProcessingCommand'].split() + [path, filename, directory, model,  file, 'cam4'])

class Modelo(threading.Thread):
    def __init__(self, modelo):
        threading.Thread.__init__(self)
        self.modelo = modelo
        self._stopevent = threading.Event()
        self.file = None
        self.online = None
        self.lock = threading.Lock()

    def run(self):
        global recording, hilos
        isOnline = self.isOnline()
        if isOnline == False:
            self.online = False
        else:
            self.online = True
            self.file = os.path.join(setting['save_directory'], self.modelo, f'{datetime.datetime.fromtimestamp(time.time()).strftime("%Y.%m.%d_%H.%M.%S")}_{self.modelo}.mp4')
            try:
                session = streamlink.Streamlink()
                streams = session.streams(f'hlsvariant://{isOnline}')
                stream = streams['best']
                fd = stream.open()
                if not isModelInListofObjects(self.modelo, recording):
                    os.makedirs(os.path.join(setting['save_directory'], self.modelo), exist_ok=True)
                    with open(self.file, 'wb') as f:
                        self.lock.acquire()
                        recording.append(self)
                        for index, hilo in enumerate(hilos):
                            if hilo.modelo == self.modelo:
                                del hilos[index]
                                break
                        self.lock.release()
                        while not (self._stopevent.isSet() or os.fstat(f.fileno()).st_nlink == 0):
                            try:
                                data = fd.read(1024)
                                f.write(data)
                            except:
                                fd.close()
                                break
                    if setting['postProcessingCommand']:
                            processingQueue.put({'model': self.modelo, 'path': self.file})
            except Exception as e:
                with open('log.log', 'a+') as f:
                    f.write(f'\n{datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S")} EXCEPTION: {e}\n')
                self.stop()
            finally:
                self.exceptionHandler()

    def exceptionHandler(self):
        self.stop()
        self.online = False
        self.lock.acquire()
        for index, hilo in enumerate(recording):
            if hilo.modelo == self.modelo:
                del recording[index]
                break
        self.lock.release()
        try:
            file = os.path.join(os.getcwd(), self.file)
            if os.path.isfile(file):
                if os.path.getsize(file) <= 1024:
                    os.remove(file)
        except Exception as e:
            with open('log.log', 'a+') as f:
                f.write(f'\n{datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S")} EXCEPTION: {e}\n')

    def isOnline(self):
        try:
            resp = requests.get(f'https://chaturbate.com/api/chatvideocontext/{self.modelo}/')
            hls_url = ''
            if 'hls_source' in resp.json():
                hls_url = resp.json()['hls_source']
            if len(hls_url):
                return hls_url
            else:
                return False
        except:
            return False

    def stop(self):
        self._stopevent.set()

class CleaningThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.interval = 0
        self.lock = threading.Lock()
        
    def run(self):
        global hilos, recording
        while True:
            self.lock.acquire()
            new_hilos = []
            for hilo in hilos:
                if hilo.is_alive() or hilo.online:
                    new_hilos.append(hilo)
            hilos = new_hilos
            self.lock.release()
            for i in range(10, 0, -1):
                self.interval = i
                time.sleep(1)

class AddModelsThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.wanted = []
        self.lock = threading.Lock()
        self.repeatedModels = []
        self.counterModel = 0

    def run(self):
        global hilos, recording
        lines = open(setting['wishlist'], 'r').read().splitlines()
        self.wanted = (x for x in lines if x)
        self.lock.acquire()
        aux = []
        for model in self.wanted:
            model = model.lower()
            if model in aux:
                self.repeatedModels.append(model)
            else:
                aux.append(model)
                self.counterModel = self.counterModel + 1
                if not isModelInListofObjects(model, hilos) and not isModelInListofObjects(model, recording):
                    thread = Modelo(model)
                    thread.start()
                    hilos.append(thread)
        for hilo in recording:
            if hilo.modelo not in aux:
                hilo.stop()
        self.lock.release()

def isModelInListofObjects(obj, lista):
    result = False
    for i in lista:
        if i.modelo == obj:
            result = True
            break
    return result

if __name__ == '__main__':
    readConfig()
    if setting['postProcessingCommand']:
        processingQueue = queue.Queue()
        postprocessingWorkers = []
        for i in range(0, setting['postProcessingThreads']):
            t = threading.Thread(target=postProcess)
            postprocessingWorkers.append(t)
            t.start()
    cleaningThread = CleaningThread()
    cleaningThread.start()
    while True:
        try:
            readConfig()
            addModelsThread = AddModelsThread()
            addModelsThread.start()
            i = 1
            for i in range(setting['interval'], 0, -1):
                cls()
                if len(addModelsThread.repeatedModels): print('The following models are more than once in wanted: [\'' + ', '.join(modelo for modelo in addModelsThread.repeatedModels) + '\']')
                print(f'{len(hilos):02d} alive Threads (1 Thread per non-recording model), cleaning dead/not-online Threads in {cleaningThread.interval:02d} seconds, {addModelsThread.counterModel:02d} models in wanted')
                print(f'Online Threads (models): {len(recording):02d}')
                print('The following models are being recorded:')
                for hiloModelo in recording: print(f'  Model: {hiloModelo.modelo}  -->  File: {os.path.basename(hiloModelo.file)}')
                print(f'Next check in {i:02d} seconds\r', end='')
                time.sleep(1)
            addModelsThread.join()
            del addModelsThread, i
        except:
            break

ahsand97 avatar Jul 18 '21 00:07 ahsand97

please help me to port for stripchat.com

senhor-R avatar Jul 30 '21 01:07 senhor-R

should be it but it's not working https://stripchat.com/api/front/v2/models/username/{self.modelo}/cam

senhor-R avatar Jul 30 '21 03:07 senhor-R

should be it but it's not working https://stripchat.com/api/front/v2/models/username/{self.modelo}/cam

Is that the URL from that page?? that API doesn't seem to have the url for the streaming directly, I'd have to dig a little bit to see how to make it work since every page is differend depending of how their API exposes the streaming links

ahsand97 avatar Jul 30 '21 13:07 ahsand97

not sure but it should be this URL, i'm learning.

senhor-R avatar Jul 30 '21 14:07 senhor-R

the URL is https://stripchat.com/api/front/v2/models/username/LaraHenao/cam?timezoneOffset=0&triggerRequest=loadCam&primaryTag=girls&uniq=391nrs5d2w67au4g

in LaraHenao I changed to {self.model} on script to get the model but it doesn't seem to work, help me please.

senhor-R avatar Jul 30 '21 14:07 senhor-R

it doesn't seem to work

That API doesn't provide the URL for the streaming, meanwhile C4 and CB's do, there's either another API where the URLs for the streaming are provided or is in the source code of the page when you do a get request to an online model (ex: http://stripchat.com/someModel)

ahsand97 avatar Jul 30 '21 15:07 ahsand97

you're right this API doesn't provide streaming URL, source code doesn't provide either, probably another API provides URL, could you give me a hint to find it.

senhor-R avatar Jul 30 '21 17:07 senhor-R

I looked into Stripchat a while back, and wrote something that was very sloppy, but never released anything publicly (nor do I plan to). I saw it brought up on here, so I figured Id chime in. This is longer than I was anticipating, but Im not active on here at all (as you may have noticed) so I wanted to include info that may be helpful to you later. Sorry if this is a bit unorganized.

The url posted above:

https://stripchat.com/api/front/v2/models/username/LaraHenao/cam?timezoneOffset=0&triggerRequest=loadCam&primaryTag=girls&uniq=391nrs5d2w67au4g

does provide all the info to record public shows. The hls stream is in the format of:

https://b-hls-17.strpst.com/hls/50961685/50961685.m3u8

if you returned the result of the request as:

result = requests.get("https://stripchat.com/api/front/v2/models/username/LaraHenao/cam?timezoneOffset=0&triggerRequest=loadCam&primaryTag=girls&uniq=391nrs5d2w67au4g").json()

you could format the hls url with:

"https://{server}.strpst.com/hls/{id}/{id}.m3u8".format(server=result['cam']['viewServers']['flashphoner-hls'], id=result['user']['user']['id']

that url (https://b-hls-17.strpst.com/hls/50961685/50961685.m3u8) can then be passed to streamlink, livestreamer, FFMPEG, or whatever else you use to capture hls streams.

Also, One thing Stripchat lacks is a WebSockets connection, or a simple single url to call to get all online models, particularly with no delay on their online status. You can repeatedly call the url and increase the page count, but it takes some time to get through the thousands of online models, and even then it has a minute or so delay on their status changes (coming online, going into private/group shows...). The best method I found was to use the "favorites" api (this is called when you go to your "favorites page) to list the models. It reflects the models status in real time. Its not instant like a WebSockets connection would be of course (since you only get updates when you call that url), but if you call it lets say ever 10 seconds or so, then you're catch their status changes with at most a 10 second delay. Also, this allows you to only monitor the status of the models you want to record. There is a limit of 99 (I believe its actually be 100 although my code says 99), so if you have less than 100 models, you can check them all in a single request instead of having to request over and over to get through all online models.

Again, my code is very sloppy, but this is the portion I used for checking that status of models.

EDIT - I apparently don't know how to format code. The line breaks aren't showing in the code below.

from urllib.parse import quote def genStatusIdUrl(ids): i=0 m = "" if len(ids) > 99: print('only 99 models can be checked at once') ids = ids[0:99] for model in ids: m += quote("modelIds[{}]".format(i)) + '=' + str(model) + '&' i+=1 url = URL.status_ids.format(m=m) return url

The "URL.status_ids" references "'https://stripchat.com/api/front/models/list?{m}&uniq='+ get_random_string(16)"

the "get_random_string(16)" is calling the following function:

import random import string def get_random_string(length): charactors = string.ascii_letters + ''.join([str(i) for i in range(0, 10)]) result_str = ''.join(random.choice(charactors) for i in range(length)) return result_str

You would pass a list of the ids of the models you wish into the genStatusIdUrl function. ie:

genStatusIdUrl([50961685, 57411509])

would return https://stripchat.com/api/front/models/list?modelIds%5B0%5D=50961685&modelIds%5B1%5D=57411509&&uniq=FRBurUADrlesqps2

which then could be checked through requests.get which would return the info in json format.

I hope this helps.

beaston02 avatar Jul 30 '21 18:07 beaston02

I hope this helps.

It did help, based on your feedback I adapted the script to work with StripChat too for @senhor-R

Click to expand!
import time
import datetime
import os
import threading
import sys
import configparser
import subprocess
import queue
import requests
import streamlink

if os.name == 'nt':
    import ctypes
    kernel32 = ctypes.windll.kernel32
    kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)

mainDir = sys.path[0]
Config = configparser.ConfigParser()
setting = {}

recording = []

hilos = []

def cls():
    os.system('cls' if os.name == 'nt' else 'clear')
    
def readConfig():
    global setting

    Config.read(mainDir + '/config.conf')
    setting = {
        'save_directory': Config.get('paths', 'save_directory'),
        'wishlist': Config.get('paths', 'wishlist'),
        'interval': int(Config.get('settings', 'checkInterval')),
        'postProcessingCommand': Config.get('settings', 'postProcessingCommand'),
        }
    try:
        setting['postProcessingThreads'] = int(Config.get('settings', 'postProcessingThreads'))
    except ValueError:
        if setting['postProcessingCommand'] and not setting['postProcessingThreads']:
            setting['postProcessingThreads'] = 1
    
    if not os.path.exists(f'{setting["save_directory"]}'):
        os.makedirs(f'{setting["save_directory"]}')

def postProcess():
    while True:
        while processingQueue.empty():
            time.sleep(1)
        parameters = processingQueue.get()
        model = parameters['model']
        path = parameters['path']
        filename = os.path.split(path)[-1]
        directory = os.path.dirname(path)
        file = os.path.splitext(filename)[0]
        subprocess.call(setting['postProcessingCommand'].split() + [path, filename, directory, model,  file, 'cam4'])

class Modelo(threading.Thread):
    def __init__(self, modelo):
        super().__init__()
        self.modelo = modelo
        self._stopevent = threading.Event()
        self.file = None
        self.online = None
        self.lock = threading.Lock()

    def run(self):
        global recording, hilos
        isOnline = self.isOnline()
        if isOnline == False:
            self.online = False
        else:
            self.online = True
            self.file = os.path.join(setting['save_directory'], self.modelo, f'{datetime.datetime.fromtimestamp(time.time()).strftime("%Y.%m.%d_%H.%M.%S")}_{self.modelo}.mp4')
            try:
                session = streamlink.Streamlink()
                streams = session.streams(f'hlsvariant://{isOnline}')
                stream = streams['best']
                fd = stream.open()
                if not isModelInListofObjects(self.modelo, recording):
                    os.makedirs(os.path.join(setting['save_directory'], self.modelo), exist_ok=True)
                    with open(self.file, 'wb') as f:
                        self.lock.acquire()
                        recording.append(self)
                        for index, hilo in enumerate(hilos):
                            if hilo.modelo == self.modelo:
                                del hilos[index]
                                break
                        self.lock.release()
                        while not (self._stopevent.isSet() or os.fstat(f.fileno()).st_nlink == 0):
                            try:
                                data = fd.read(1024)
                                f.write(data)
                            except:
                                fd.close()
                                break
                    if setting['postProcessingCommand']:
                            processingQueue.put({'model': self.modelo, 'path': self.file})
            except Exception as e:
                with open('log.log', 'a+') as f:
                    f.write(f'\n{datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S")} EXCEPTION: {e}\n')
                self.stop()
            finally:
                self.exceptionHandler()

    def exceptionHandler(self):
        self.stop()
        self.online = False
        self.lock.acquire()
        for index, hilo in enumerate(recording):
            if hilo.modelo == self.modelo:
                del recording[index]
                break
        self.lock.release()
        try:
            file = os.path.join(os.getcwd(), self.file)
            if os.path.isfile(file):
                if os.path.getsize(file) <= 1024:
                    os.remove(file)
        except Exception as e:
            with open('log.log', 'a+') as f:
                f.write(f'\n{datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S")} EXCEPTION: {e}\n')

    def isOnline(self):
        try:
            resp = requests.get(f'https://stripchat.com/api/front/v2/models/username/{self.modelo}/cam').json()
            hls_url = ''
            if 'cam' in resp.keys():
                if {'isCamAvailable', 'streamName', 'viewServers'} <= resp['cam'].keys():
                    if 'flashphoner-hls' in resp['cam']['viewServers'].keys():
                        hls_url = f'https://b-{resp["cam"]["viewServers"]["flashphoner-hls"]}.strpst.com/hls/{resp["cam"]["streamName"]}/{resp["cam"]["streamName"]}.m3u8'
            if len(hls_url):
                return hls_url
            else:
                return False
        except:
            return False

    def stop(self):
        self._stopevent.set()

class CleaningThread(threading.Thread):
    def __init__(self):
        super().__init__()
        self.interval = 0
        self.lock = threading.Lock()
        
    def run(self):
        global hilos, recording
        while True:
            self.lock.acquire()
            new_hilos = []
            for hilo in hilos:
                if hilo.is_alive() or hilo.online:
                    new_hilos.append(hilo)
            hilos = new_hilos
            self.lock.release()
            for i in range(10, 0, -1):
                self.interval = i
                time.sleep(1)

class AddModelsThread(threading.Thread):
    def __init__(self):
        super().__init__()
        self.wanted = []
        self.lock = threading.Lock()
        self.repeatedModels = []
        self.counterModel = 0

    def run(self):
        global hilos, recording
        lines = open(setting['wishlist'], 'r').read().splitlines()
        self.wanted = (x for x in lines if x)
        self.lock.acquire()
        aux = []
        for model in self.wanted:
            model = model.lower()
            if model in aux:
                self.repeatedModels.append(model)
            else:
                aux.append(model)
                self.counterModel = self.counterModel + 1
                if not isModelInListofObjects(model, hilos) and not isModelInListofObjects(model, recording):
                    thread = Modelo(model)
                    thread.start()
                    hilos.append(thread)
        for hilo in recording:
            if hilo.modelo not in aux:
                hilo.stop()
        self.lock.release()

def isModelInListofObjects(obj, lista):
    result = False
    for i in lista:
        if i.modelo == obj:
            result = True
            break
    return result

if __name__ == '__main__':
    readConfig()
    if setting['postProcessingCommand']:
        processingQueue = queue.Queue()
        postprocessingWorkers = []
        for i in range(0, setting['postProcessingThreads']):
            t = threading.Thread(target=postProcess)
            postprocessingWorkers.append(t)
            t.start()
    cleaningThread = CleaningThread()
    cleaningThread.start()
    while True:
        try:
            readConfig()
            addModelsThread = AddModelsThread()
            addModelsThread.start()
            i = 1
            for i in range(setting['interval'], 0, -1):
                cls()
                if len(addModelsThread.repeatedModels): print('The following models are more than once in wanted: [\'' + ', '.join(modelo for modelo in addModelsThread.repeatedModels) + '\']')
                print(f'{len(hilos):02d} alive Threads (1 Thread per non-recording model), cleaning dead/not-online Threads in {cleaningThread.interval:02d} seconds, {addModelsThread.counterModel:02d} models in wanted')
                print(f'Online Threads (models): {len(recording):02d}')
                print('The following models are being recorded:')
                for hiloModelo in recording: print(f'  Model: {hiloModelo.modelo}  -->  File: {os.path.basename(hiloModelo.file)}')
                print(f'Next check in {i:02d} seconds\r', end='')
                time.sleep(1)
            addModelsThread.join()
            del addModelsThread, i
        except:
            break

ahsand97 avatar Jul 30 '21 18:07 ahsand97

thank you very much, it's working. thank you very much for your help

senhor-R avatar Jul 30 '21 19:07 senhor-R

Does not download anything

1581-netizen avatar Jul 04 '22 14:07 1581-netizen

Does not download anything

still working

senhor-R avatar Jul 15 '22 13:07 senhor-R

Does not download anything

still working

It detects the models in line, but downloads absolutely nothing. What can I check to understand the malfunction? Does the version you previously posted work?

1581-netizen avatar Aug 01 '22 11:08 1581-netizen

I try files on https://github.com/horacio9a/CAM4Recorder and all working OK for me! Sent from Mail for Windows From: 1581-netizenSent: 01 August 2022 13:02To: beaston02/CAM4RecorderCc: horacio9a; MentionSubject: Re: [beaston02/CAM4Recorder] broken ;o( (#15) Does not download anythingstill workingIt detects the models in line, but downloads absolutely nothing. What can I check to understand the malfunction? Does the version you previously posted work?—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: ***@***.***> 

horacio9a avatar Aug 01 '22 18:08 horacio9a

Thank you for you code for Stripchat! All working. But I have a memory leak, size in RAM grows indefinitely.

77Mike7 avatar Oct 16 '22 17:10 77Mike7