├── CAM4Recorder.py ├── README.md ├── config.conf └── wanted.txt /CAM4Recorder.py: -------------------------------------------------------------------------------- 1 | import requests, time, datetime, os, threading, sys, configparser 2 | from livestreamer import Livestreamer 3 | if os.name == 'nt': 4 | import ctypes 5 | kernel32 = ctypes.windll.kernel32 6 | kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7) 7 | from subprocess import call 8 | from queue import Queue 9 | 10 | mainDir = sys.path[0] 11 | Config = configparser.ConfigParser() 12 | setting = {} 13 | def readConfig(): 14 | global setting 15 | global filter 16 | Config.read(mainDir + "/config.conf") 17 | setting = { 18 | 'save_directory': Config.get('paths', 'save_directory'), 19 | 'wishlist': Config.get('paths', 'wishlist'), 20 | 'interval': int(Config.get('settings', 'checkInterval')), 21 | 'postProcessingCommand': Config.get('settings', 'postProcessingCommand'), 22 | } 23 | try: 24 | setting['postProcessingThreads'] = int(Config.get('settings', 'postProcessingThreads')) 25 | except ValueError: 26 | if setting['postProcessingCommand'] and not setting['postProcessingThreads']: 27 | setting['postProcessingThreads'] = 1 28 | 29 | if not os.path.exists("{path}".format(path=setting['save_directory'])): 30 | os.makedirs("{path}".format(path=setting['save_directory'])) 31 | recording = [] 32 | UserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Mobile Safari/537.36" 33 | offline = False 34 | 35 | def getOnlineModels(page): 36 | i = 1 37 | while i < 5: 38 | try: 39 | sys.stdout.write("\033[K") 40 | print("{} model(s) are being recorded. Checking for models to record (page {})".format(len(recording), page)) 41 | sys.stdout.write("\033[K") 42 | print("the following models are being recorded: {}".format(recording), end="\r") 43 | result = requests.get("https://www.cam4.com/directoryCams?directoryJson=true&online=true&url=true&page={}".format(page)).json() 44 | return result 45 | except: 46 | i = i + 1 47 | sys.stdout.write("\033[F") 48 | 49 | def startRecording(model): 50 | try: 51 | model = model.lower() 52 | resp = requests.get('https://www.cam4.com/' + model, headers={'user-agent':'UserAgent'}).text.splitlines() 53 | videoPlayUrl = "" 54 | videoAppUrl = "" 55 | for line in resp: 56 | if "videoPlayUrl" in line: 57 | for part in line.split("&"): 58 | if "videoPlayUrl" in part and videoPlayUrl == "": 59 | videoPlayUrl = part[13:] 60 | elif "videoAppUrl" in part and videoAppUrl == "": 61 | videoAppUrl = part.split("//")[1] 62 | session = Livestreamer() 63 | session.set_option('http-headers', "referer=https://www.cam4.com/{}".format(model)) 64 | streams = session.streams("hlsvariant://https://{}/amlst:{}_aac/playlist.m3u8?referer=www.cam4.com×tamp={}" 65 | .format(videoAppUrl, videoPlayUrl, str(int(time.time() * 1000)))) 66 | stream = streams["best"] 67 | fd = stream.open() 68 | ts = time.time() 69 | st = datetime.datetime.fromtimestamp(ts).strftime("%Y.%m.%d_%H.%M.%S") 70 | file = os.path.join(setting['save_directory'], model, "{st}_{model}.mp4".format(path=setting['save_directory'], model=model, 71 | st=st)) 72 | os.makedirs(os.path.join(setting['save_directory'], model), exist_ok=True) 73 | with open(file, 'wb') as f: 74 | recording.append(model) 75 | while True: 76 | try: 77 | data = fd.read(1024) 78 | f.write(data) 79 | except: 80 | break 81 | if setting['postProcessingCommand']: 82 | processingQueue.put({'model': model, 'path': file}) 83 | finally: 84 | if model in recording: 85 | recording.remove(model) 86 | 87 | def postProcess(): 88 | while True: 89 | while processingQueue.empty(): 90 | time.sleep(1) 91 | parameters = processingQueue.get() 92 | model = parameters['model'] 93 | path = parameters['path'] 94 | filename = os.path.split(path)[-1] 95 | directory = os.path.dirname(path) 96 | file = os.path.splitext(filename)[0] 97 | call(setting['postProcessingCommand'].split() + [path, filename, directory, model, file, 'cam4']) 98 | 99 | if __name__ == '__main__': 100 | readConfig() 101 | if setting['postProcessingCommand']: 102 | processingQueue = Queue() 103 | postprocessingWorkers = [] 104 | for i in range(0, setting['postProcessingThreads']): 105 | t = threading.Thread(target=postProcess) 106 | postprocessingWorkers.append(t) 107 | t.start() 108 | while True: 109 | readConfig() 110 | wanted = [] 111 | i = 1 112 | with open(setting['wishlist']) as f: 113 | for model in f: 114 | models = model.split() 115 | for theModel in models: 116 | wanted.append(theModel.lower()) 117 | online = [] 118 | while not offline: 119 | results = getOnlineModels(i) 120 | if len(results['users']) >= 1: 121 | online.extend([user['username'].lower() for user in results['users']]) 122 | else: 123 | offline = True 124 | i = i + 1 125 | sys.stdout.write("\033[F") 126 | offline = False 127 | for model in list(set(set(online) - set(recording)).intersection(set(wanted))): 128 | thread = threading.Thread(target=startRecording, args=(model.lower(),)) 129 | thread.start() 130 | for i in range(setting['interval'], 0, -1): 131 | sys.stdout.write("\033[K") 132 | print("{} model(s) are being recorded. Next check in {} seconds".format(len(recording), i)) 133 | sys.stdout.write("\033[K") 134 | print("the following models are being recorded: {}".format(recording), end="\r") 135 | time.sleep(1) 136 | sys.stdout.write("\033[F") 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CAM4Recorder 2 | 3 | This is script to automate the recording of public webcam shows from cam4. 4 | 5 | 6 | ## Requirements: 7 | 8 | A machine running a recent Linux distro, I have tested this on Debian (7+8) and Mac OS X (10.10.4) 9 | 10 | Python 3.5 or newer https://www.python.org/downloads/release/python-362/ 11 | 12 | ## installing and Cloning with the required modules: 13 | 14 | to install the required modules, run: (For Debain/Ubuntu) 15 | ``` 16 | sudo apt-install update && sudo apt install upgrade 17 | sudo apt-install python3-pip && sudo pip3 install livestreamer && sudo apt-get install git 18 | cd /home/yourusername 19 | git clone https://github.com/beaston02/CAM4Recorder 20 | cd CAM4Recorder 21 | (Optional) sudo apt-install gitclone && sudo apt-install ffmpeg 22 | ``` 23 | 24 | to install the required modules, run: (For CentOS/Red Hat/Fedora) 25 | ``` 26 | yum update 27 | yum upgrade 28 | yum python3-pip 29 | pip3 install livestreamer 30 | yum install git clone 31 | cd /home/yourusername 32 | git clone https://github.com/beaston02/CAM4Recorder 33 | cd CAM4Recorder 34 | (Optional) yum install ffmpeg 35 | ``` 36 | 37 | to install required modules, run: (For Arch Linux, Antergos, Manjaro, etc.) 38 | ``` 39 | pacman -Syuu 40 | pacman -S python-pip git 41 | pip install livestreamer 42 | cd /home/yourusername 43 | git clone https://github.com/beaston02/CAM4Recorder 44 | cd CAM4Recorder 45 | (Optional maybe Feature releases?) sudo apt-install ffmpeg 46 | 47 | ``` 48 | ## Config and Run 49 | 50 | Configure the settings in the config file. Set the path to the wishlist and save_directory. You can adjust the check interval, or leave it at 20 seconds 51 | 52 | Add models to the "wanted.txt" file (only one model per line). The model should match the models name in their chatrooms URL (https://www.cam4.com/{modelname}). 53 | 54 | you can create your own post processing script to run on the completed files and set the command and number of threads (the number of files which could be processed at one time) in the config file. The arguments that are passed to the script are: 55 | 1 = full file path (ie: /Users/Joe/cam4/hannah/2017.07.26_19.34.47_hannah.mp4) 56 | 2 = filename (ie : 2017.07.26_19.34.47_hannah.mp4) 57 | 3 = directory (ie : /Users/Joe/cam4/hannah/) 58 | 4 = models name (ie: hannah) 59 | 5 = filename without the extension (ie: 2017.07.26_19.34.47_hannah) 60 | 6 = 'cam4' - thats it, just 'cam4' to identify the site 61 | -------------------------------------------------------------------------------- /config.conf: -------------------------------------------------------------------------------- 1 | [paths] 2 | wishlist = /Users/Joe/cam4.txt 3 | save_directory = /Users/Joe/cam4 4 | 5 | [settings] 6 | checkInterval = 20 7 | 8 | # (OPTIONAL) - leave blank if you dont want to run a post processing script on the files 9 | # You can set a command to be ran on the file once it is completed. This can be any sort of a script you would like. 10 | # You can create a script to convert the video via ffmpeg to make it compatible for certain devices, create a contact sheet of the video 11 | # upload the video to a cloud storage drive via rclone, or whatever else you see fit. 12 | # set the string to be the same as you you would type into terminal to call the script manually. 13 | # The peramaters which will be passed to the script are as follows: 14 | # 1 = full file path (ie: /Users/Joe/cam4/hannah/2017.07.26_19.34.47_hannah.mp4) 15 | # 2 = filename (ie : 2017.07.26_19.34.47_hannah.mp4) 16 | # 3 = directory (ie : /Users/Joe/cam4/hannah/) 17 | # 4 = models name (ie: hannah) 18 | # 5 = filename without the extension (ie: 2017.07.26_19.34.47_hannah) 19 | # 6 = 'cam4' - thats it, just 'cam4' to identify the site. 20 | # to call a bash script called "MoveToGoogleDrive.sh" and located in the user Joes home directory, you would use: 21 | # postProcessingCommand = "bash /Users/Joe/home/MoveToGoogleDrive.sh" 22 | # this script will be ran on the files "download location" prior to it being moved to its "completed location". 23 | # The moving of the file will not take place if a post processing script is ran, so if you want to move it once it is completed, do so through commands in the post processing script. 24 | 25 | postProcessingCommand = 26 | 27 | 28 | # Because depending on what the post processing script does, it may be demanding on the system. 29 | # Set the maximum number of concurrent post processing scripts you would like to be ran at one time. 30 | # (required if using a post processing script) 31 | 32 | postProcessingThreads = 33 | -------------------------------------------------------------------------------- /wanted.txt: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------