├── screenshot0.jpg ├── screenshot1.jpg ├── screenshot2.jpg ├── C4_Model.txt ├── Requirements.txt ├── config.ini ├── c4.bat ├── cam4_247.py ├── README.md ├── progress_170.py ├── progress_211.py └── cam4_mm.py /screenshot0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horacio9a/streamlink-cam4/HEAD/screenshot0.jpg -------------------------------------------------------------------------------- /screenshot1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horacio9a/streamlink-cam4/HEAD/screenshot1.jpg -------------------------------------------------------------------------------- /screenshot2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horacio9a/streamlink-cam4/HEAD/screenshot2.jpg -------------------------------------------------------------------------------- /C4_Model.txt: -------------------------------------------------------------------------------- 1 | diamante_08 2 | 007fuckass 3 | gangxxxbang 4 | cristalcali 5 | paolamendoza 6 | -------------------------------------------------------------------------------- /Requirements.txt: -------------------------------------------------------------------------------- 1 | DateTime==4.3 2 | requests==2.25.1 3 | Command==0.1.0 4 | colorama==0.4.4 5 | termcolor==1.1.0 6 | configparser==4.0.2 7 | livestreamer==1.12.2 8 | streamlink==1.7.0 9 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [folders] 2 | output_folder_BC: D:/Videos/BC/ 3 | output_folder_C4: D:/Videos/Cam4/ 4 | output_folder_CS: D:/Videos/CamSoda/ 5 | output_folder_MFC: D:/Videos/MFC/ 6 | output_folder_SC: D:/Videos/StripChat/ 7 | output_folder_YT: D:/Videos/YT/ 8 | [files] 9 | player: "C:/Program Files/VideoLAN/VLC/vlc.exe" 10 | ffmpeg: C:/Windows/ffmpeg.exe 11 | streamlink: "C:/Program Files (x86)/Streamlink/bin/streamlink.exe" 12 | livestreamer: C:/Python27/Scripts/livestreamer.exe 13 | youtube: C:/Windows/youtube-dl.exe 14 | -------------------------------------------------------------------------------- /c4.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL EnableDelayedExpansion 3 | :START 4 | CLS 5 | SET n=0 6 | FOR /F "tokens=*" %%A IN (C:\Windows\C4_Model.txt) DO ( 7 | SET /A n=n+1 8 | SET _fav!n!=%%A 9 | ECHO !n! %%A 10 | ) 11 | ECHO. 12 | SET /P MODEL=Select CAM4 Model (%M% %MODEL%): 13 | FOR /L %%f IN (1,1,!n!) DO ( 14 | IF /I '%MODEL%'=='%%f' SET M=%%f 15 | ) 16 | SET n=0 17 | FOR /F "tokens=*" %%A IN (C:\Windows\C4_Model.txt) DO ( 18 | SET /A n=n+1 19 | IF !n!==%M% SET MODEL=%%A 20 | ) 21 | ECHO. 22 | SET /P MODE=EXIT(3) START(2) C4-SL-R(24/7)(1) C4-SL(0)(ENTER)(%MODE%): 23 | IF "%MODE%"=="" GOTO C4-SL 24 | IF "%MODE%"=="0" GOTO C4-SL 25 | IF "%MODE%"=="1" GOTO C4-SL-R 26 | IF "%MODE%"=="2" GOTO START 27 | IF "%MODE%"=="3" GOTO EXIT 28 | :C4-SL 29 | ECHO. 30 | CLS && ECHO ##################################################### 31 | ECHO ### C4-SL ##### R E C O R D I N G - C A M 4 ##### 32 | SET hour=%time:~0,2% 33 | IF "%hour:~0,1%" == " " SET hour=0%hour:~1,1% 34 | SET NOW=%date:~4,2%%date:~7,2%%date:~10,4%-%hour%%time:~3,2%%time:~6,2% 35 | FOR /f "tokens=1-2 delims=/:" %%a IN ('time /t') DO (set mytime=%%a%%b) 36 | SET OUT_DIR=C:\Videos\Cam4\ 37 | SET FILENAME=%MODEL%_C4_%NOW%.flv 38 | SET OUTPUT=%OUT_DIR%%FILENAME% 39 | SET FNAME=######## %FILENAME% ### %M% ############################## 40 | SET _FNAME_=%FNAME:~5,53% 41 | IF EXIST "%OUT_DIR%" (ECHO %_FNAME_%) ELSE (MD "%OUT_DIR%" 42 | ECHO %_FNAME_%) 43 | ECHO ##################################################### 44 | ECHO. 45 | COLOR 0F 46 | cd/ 47 | cd Python27/Scripts 48 | START STREAMLINK "https://www.cam4.com/%MODEL%/" 49 | PAUSE 50 | GOTO START 51 | :C4-SL-R 52 | ECHO. 53 | CLS && ECHO ##################################################### 54 | ECHO ### C4-SL-R ### R E C O R D I N G - 2 4 / 7 ##### 55 | SET hour=%time:~0,2% 56 | IF "%hour:~0,1%" == " " SET hour=0%hour:~1,1% 57 | SET NOW=%date:~4,2%%date:~7,2%%date:~10,4%-%hour%%time:~3,2%%time:~6,2% 58 | FOR /f "tokens=1-2 delims=/:" %%a IN ('time /t') DO (set mytime=%%a%%b) 59 | SET OUT_DIR=C:\Videos\Cam4\ 60 | SET FILENAME=%MODEL%_C4_%NOW%.flv 61 | SET OUTPUT=%OUT_DIR%%FILENAME% 62 | SET FNAME=######## %FILENAME% ### %M% ############################## 63 | SET _FNAME_=%FNAME:~5,53% 64 | IF EXIST "%OUT_DIR%" (ECHO %_FNAME_%) ELSE (MD "%OUT_DIR%" 65 | ECHO %_FNAME_%) 66 | ECHO ##################################################### 67 | ECHO. 68 | COLOR 0F 69 | cd/ 70 | cd Python27/Scripts 71 | STREAMLINK "https://www.cam4.com/%MODEL%/" 72 | TIMEOUT 30 73 | GOTO C4-SL-R 74 | :EXIT 75 | GOTO :EOF 76 | ENDLOCAL 77 | -------------------------------------------------------------------------------- /cam4_247.py: -------------------------------------------------------------------------------- 1 | # Cam4 Streamlink LIVESTREAMER Remote 24/7 Plugin v.2.0.0 by @horacio9a for Python 2.7.18 2 | # coding: utf-8 3 | 4 | import os, sys, re, time, command 5 | 6 | reload(sys) 7 | sys.setdefaultencoding('utf-8') 8 | from streamlink.plugin import Plugin 9 | from streamlink.plugin.api import validate 10 | from streamlink.stream import HLSStream 11 | from datetime import datetime 12 | from colorama import init, Fore, Back, Style 13 | from termcolor import colored 14 | import ConfigParser 15 | Config = ConfigParser.ConfigParser() 16 | Config.read('config.ini') 17 | 18 | init() 19 | 20 | STREAM_INFO = "https://www.cam4.com/rest/v1.0/profile/{0}/streamInfo" 21 | INFO_URL = "https://www.cam4.com/rest/v1.0/search/performer/{0}" 22 | PROFILE_URL = "https://www.cam4.com/rest/v1.0/profile/{0}/info" 23 | 24 | _url_re = re.compile(r"https?://(\w+\.)?cam4\.com/(?P\w+)") 25 | 26 | class Cam4(Plugin): 27 | @classmethod 28 | def can_handle_url(cls, url): 29 | return _url_re.match(url) 30 | 31 | def _get_streams(self): 32 | match = _url_re.match(self.url) 33 | username = match.group("username") 34 | 35 | res = self.session.http.get(INFO_URL.format(username)) 36 | data = self.session.http.json(res) 37 | print 38 | self.logger.info("Username: {0}".format(data["username"])) 39 | name = data["username"] 40 | online = data["online"] 41 | self.logger.info("Online: {0}".format(data["online"])) 42 | self.logger.info("Country: {0}".format(data["country"])) 43 | res = self.session.http.get(PROFILE_URL.format(username)) 44 | data = self.session.http.json(res) 45 | self.logger.info("City: {0}".format(data["city"])) 46 | self.logger.info("Sex Preference: {0}".format(data["sexPreference"])) 47 | self.logger.info("Ethnicity: {0}".format(data["ethnicity"])) 48 | self.logger.info("Main Language: {0}".format(data["mainLanguage"])) 49 | self.logger.info("Breast Size: {0}".format(data["breastSize"])) 50 | self.logger.info("Birthdate: {0}".format(data["birthdate"])) 51 | self.logger.info("Age: {0}".format(int((datetime.now() - datetime.strptime(data["birthdate"], "%Y-%m-%d")).days / 365))) 52 | 53 | if online: 54 | res = self.session.http.get(STREAM_INFO.format(username)) 55 | data = self.session.http.json(res) 56 | if data["canUseCDN"]: 57 | hlsurl = data["cdnURL"] 58 | timestamp = str(time.strftime("%d%m%Y-%H%M%S")) 59 | stime = str(time.strftime("%H:%M:%S")) 60 | path = Config.get('folders', 'output_folder_C4') 61 | fn = name + '_C4_' + timestamp + '.mp4' 62 | pf = (path + fn) 63 | livestreamer = Config.get('files', 'livestreamer') 64 | print 65 | print (colored(' => LS-247 => {} ( Size @ Speed ) <=', 'white', 'on_red')).format(fn) 66 | print 67 | command = ('{} hlsvariant://{} best -Q -o {}'.format(livestreamer,hlsurl,pf)) 68 | os.system(command) 69 | sys.exit() 70 | 71 | else: 72 | print 73 | print(colored(' => Performer is in PRIVATE SHOW <=',"white","on_red")) 74 | print 75 | print(colored(' => END <= ', 'white','on_blue')) 76 | time.sleep(3) 77 | sys.exit() 78 | 79 | else: 80 | print 81 | print(colored(' => Performer is OFFLINE <=', 'white','on_red')) 82 | print 83 | print(colored(' => END <=', 'white','on_blue')) 84 | time.sleep(3) 85 | sys.exit() 86 | 87 | __plugin__ = Cam4 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | What's new? 2 | =========== 3 | streamlink-cam4 lets you follow and archive your selected models shows on www.cam4.com. 4 | This streamlink-cam4 plugin that you see here use the C4_Model.txt where is a list of your favorites C4 models. 5 | You don't need to be registered cam4 user for recording models with this streamlink-cam4 plugin. 6 | I adapted existing new streamlink plugin in two versions like before. 7 | The first version is `cam4_247.py` for 24/7 recording of your favorite performer. 8 | The second version is `cam4_mm.py` for online multi mode recording (ffmpeg, streamlink, livestream or youtube-dl) 9 | Attention, both files should be renamed to `cam.py` and moved to the directory `C:\Python27\Lib\site-packages\streamlink\plugins\` 10 | Now it is possible to look at a certain model first and when we decide to record it is necessary to stop the 'preview' with Ctrl-C and then a menu with various options for recording or exit appears. 11 | 12 | Requirements 13 | ============ 14 | 1. [Python 2.7.18](https://www.python.org/ftp/python/2.7.18/python-2.7.18.msi) 15 | 2. [Livestreamer](https://github.com/chrippa/livestreamer/releases) last version is 1.12.2 16 | 3. [Streamlink](https://github.com/streamlink/streamlink/releases) last version for Python27 is 1.7.0 but you can install also last version independently in in `C:/Program Files (x86)/Streamlink/bin/streamlink.exe` 17 | 4. [ffmpeg & ffplay](https://ffmpeg.zeranoe.com/builds/) It is recommended to install the latest version, default location is `C:/Windows` 18 | 5. [youtube-dl](https://github.com/rg3/youtube-dl) who must be somewere in the path, default location is `C:/Windows` 19 | 20 | Setup 21 | ===== 22 | 1. Install requirements `pip install -r Requirements.txt` or better one by one like this example `pip install streamlink==1.7.0` 23 | 2. Download and unpack the [code](https://codeload.github.com/horacio9a/streamlink-cam4/zip/master). 24 | 3. C4 streamlink-cam4 plugin `cam4.py` must replace existing plugin in `C:/Python27/Lib/site-packages/streamlink/plugins/cam4.py`. It should be noted that cam4 can only be one plugin and therefore need to be renamed cam4_.py in cam4.py if you need 'online' version. 25 | 4. Batch script `c4.bat` can be anywhere (default is `C:/Windows`). 26 | 5. C4 favourite list `C4_Model.txt` can be anywhere (default is `C:/Windows`). 27 | 6. `config.ini` must be in `C:/Python27/Scripts/config.ini`. Edit `config.ini` depending on your situation or accept default data. You must enter the correct path on your computer for all the files that are there. In that case, those files do not have to be in the path. 28 | 29 | Running & Output 30 | ================ 31 | It's best to use 'Command Promt' window to install `Requirements.txt`. 32 | For easier use of this plugin it would be good to make a shortcut and put it in the task bar for easier startup. 33 | However, if you want to record a certain model permanently (24/7), then you need to use `c4.bat`, options number `1` and `0` for online multi mode use. 34 | For permanently 24/7 recording more than one model at the same time you need to start multiples copy of `c4.bat`. 35 | For stop recording use Ctrl-C or by clicking 'x' at the top right corner of the script window if Ctrl-C does not work. 36 | For a better view you can replace the file 'progress.py' in the streamlink and livestreamer installation 37 | 38 | 1. `C:\Program Files (x86)\Streamlink\pkgs\streamlink_cli\utils\progress.py => progress_211` 39 | 2. `C:\Python27\Lib\site-packages\streamlink_cli\utils\progress.py => progress_170` 40 | 3. `C:\Python27\Lib\site-packages\livestreamer_cli\utils\progress.py => progress_170` 41 | 42 | The initial layout of the `c4.bat` script: 43 | ![alt screenshot](./screenshot0.jpg) 44 | 45 | The look of the default `cam4_247.py` (must be renamed to `cam4.py`) for 24/7 permanent one model tracking: 46 | ![alt screenshot](./screenshot1.jpg) 47 | 48 | If you don't want to keep track of a specific model, then you can use `cam4_mm.py` (must be renamed to `cam4.py`) which has many options and it is possible to use the same script window to start many models.: 49 | ![alt screenshot](./screenshot2.jpg) 50 | -------------------------------------------------------------------------------- /progress_170.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from collections import deque 4 | from time import time 5 | 6 | from ..compat import is_win32, get_terminal_size 7 | 8 | PROGRESS_FORMATS = ( 9 | "[download] => {prefix} ({written} @ {speed}/s) ", 10 | ) 11 | 12 | # widths generated from 13 | # http://www.unicode.org/Public/4.0-Update/EastAsianWidth-4.0.0.txt 14 | widths = [ 15 | (13, 1), (15, 0), (126, 1), (159, 0), (687, 1), (710, 0), # noqa: E241 16 | (711, 1), (727, 0), (733, 1), (879, 0), (1154, 1), (1161, 0), # noqa: E241 17 | (4347, 1), (4447, 2), (7467, 1), (7521, 0), (8369, 1), (8426, 0), # noqa: E241 18 | (9000, 1), (9002, 2), (11021, 1), (12350, 2), (12351, 1), (12438, 2), # noqa: E241 19 | (12442, 0), (19893, 2), (19967, 1), (55203, 2), (63743, 1), (64106, 2), # noqa: E241 20 | (65039, 1), (65059, 0), (65131, 2), (65279, 1), (65376, 2), (65500, 1), # noqa: E241 21 | (65510, 2), (120831, 1), (262141, 2), (1114109, 1) # noqa: E241 22 | ] 23 | 24 | 25 | 26 | def get_width(o): 27 | """Returns the screen column width for unicode ordinal.""" 28 | for num, wid in widths: 29 | if o <= num: 30 | return wid 31 | return 1 32 | 33 | 34 | def terminal_width(value): 35 | """Returns the width of the string it would be when displayed.""" 36 | if isinstance(value, bytes): 37 | value = value.decode("utf8", "ignore") 38 | return sum(map(get_width, map(ord, value))) 39 | 40 | 41 | def get_cut_prefix(value, max_len): 42 | """Drops Characters by unicode not by bytes.""" 43 | should_convert = isinstance(value, bytes) 44 | if should_convert: 45 | value = value.decode("utf8", "ignore") 46 | for i in range(len(value)): 47 | if terminal_width(value[i:]) <= max_len: 48 | break 49 | return value[i:].encode("utf8", "ignore") if should_convert else value[i:] 50 | 51 | 52 | def print_inplace(msg): 53 | """Clears out the previous line and prints a new one.""" 54 | term_width = get_terminal_size().columns 55 | spacing = term_width - terminal_width(msg) 56 | 57 | # On windows we need one less space or we overflow the line for some reason. 58 | if is_win32: 59 | spacing -= 1 60 | 61 | sys.stderr.write("\r{0}".format(msg)) 62 | sys.stderr.write(" " * max(0, spacing)) 63 | sys.stderr.flush() 64 | 65 | 66 | def format_filesize(size): 67 | """Formats the file size into a human readable format.""" 68 | for suffix in ("bytes", "KB", "MB", "GB", "TB"): 69 | if size < 1024.0: 70 | if suffix in ("GB", "TB"): 71 | return "{0:3.2f} {1}".format(size, suffix) 72 | else: 73 | return "{0:3.1f} {1}".format(size, suffix) 74 | 75 | size /= 1024.0 76 | 77 | 78 | def format_time(elapsed): 79 | """Formats elapsed seconds into a human readable format.""" 80 | hours = int(elapsed / (60 * 60)) 81 | minutes = int((elapsed % (60 * 60)) / 60) 82 | seconds = int(elapsed % 60) 83 | 84 | rval = "" 85 | if hours: 86 | rval += "{0}h".format(hours) 87 | 88 | if elapsed > 60: 89 | rval += "{0}m".format(minutes) 90 | 91 | rval += "{0}s".format(seconds) 92 | return rval 93 | 94 | 95 | def create_status_line(**params): 96 | """Creates a status line with appropriate size.""" 97 | max_size = get_terminal_size().columns - 1 98 | 99 | for fmt in PROGRESS_FORMATS: 100 | status = fmt.format(**params) 101 | 102 | if len(status) <= max_size: 103 | break 104 | 105 | return status 106 | 107 | 108 | def progress(iterator, prefix): 109 | """Progress an iterator and updates a pretty status line to the terminal. 110 | 111 | The status line contains: 112 | - Amount of data read from the iterator 113 | - Time elapsed 114 | - Average speed, based on the last few seconds. 115 | """ 116 | if terminal_width(prefix) > 25: 117 | prefix = (get_cut_prefix(prefix, 50)) 118 | speed_updated = start = time() 119 | speed_written = written = 0 120 | speed_history = deque(maxlen=5) 121 | 122 | for data in iterator: 123 | yield data 124 | 125 | now = time() 126 | elapsed = now - start 127 | written += len(data) 128 | 129 | speed_elapsed = now - speed_updated 130 | if speed_elapsed >= 0.5: 131 | speed_history.appendleft(( 132 | written - speed_written, 133 | speed_updated, 134 | )) 135 | speed_updated = now 136 | speed_written = written 137 | 138 | speed_history_written = sum(h[0] for h in speed_history) 139 | speed_history_elapsed = now - speed_history[-1][1] 140 | speed = speed_history_written / speed_history_elapsed 141 | 142 | status = create_status_line( 143 | prefix=prefix, 144 | written=format_filesize(written), 145 | elapsed=format_time(elapsed), 146 | speed=format_filesize(speed) 147 | ) 148 | print_inplace(status) 149 | sys.stderr.write("\n") 150 | sys.stderr.flush() 151 | -------------------------------------------------------------------------------- /progress_211.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from collections import deque 3 | from shutil import get_terminal_size 4 | from time import time 5 | 6 | from streamlink_cli.compat import is_win32 7 | 8 | PROGRESS_FORMATS = ( 9 | "[download] => {prefix} ({written} @ {speed}/s) ", 10 | ) 11 | 12 | # widths generated from 13 | # http://www.unicode.org/Public/4.0-Update/EastAsianWidth-4.0.0.txt 14 | widths = [ 15 | (13, 1), (15, 0), (126, 1), (159, 0), (687, 1), (710, 0), # noqa: E241 16 | (711, 1), (727, 0), (733, 1), (879, 0), (1154, 1), (1161, 0), # noqa: E241 17 | (4347, 1), (4447, 2), (7467, 1), (7521, 0), (8369, 1), (8426, 0), # noqa: E241 18 | (9000, 1), (9002, 2), (11021, 1), (12350, 2), (12351, 1), (12438, 2), # noqa: E241 19 | (12442, 0), (19893, 2), (19967, 1), (55203, 2), (63743, 1), (64106, 2), # noqa: E241 20 | (65039, 1), (65059, 0), (65131, 2), (65279, 1), (65376, 2), (65500, 1), # noqa: E241 21 | (65510, 2), (120831, 1), (262141, 2), (1114109, 1) # noqa: E241 22 | ] 23 | 24 | 25 | def get_width(o): 26 | """Returns the screen column width for unicode ordinal.""" 27 | for num, wid in widths: 28 | if o <= num: 29 | return wid 30 | return 1 31 | 32 | 33 | def terminal_width(value): 34 | """Returns the width of the string it would be when displayed.""" 35 | if isinstance(value, bytes): 36 | value = value.decode("utf8", "ignore") 37 | return sum(map(get_width, map(ord, value))) 38 | 39 | 40 | def get_cut_prefix(value, max_len): 41 | """Drops Characters by unicode not by bytes.""" 42 | should_convert = isinstance(value, bytes) 43 | if should_convert: 44 | value = value.decode("utf8", "ignore") 45 | for i in range(len(value)): 46 | if terminal_width(value[i:]) <= max_len: 47 | break 48 | return value[i:].encode("utf8", "ignore") if should_convert else value[i:] 49 | 50 | 51 | def print_inplace(msg): 52 | """Clears out the previous line and prints a new one.""" 53 | term_width = get_terminal_size().columns 54 | spacing = term_width - terminal_width(msg) 55 | 56 | # On windows we need one less space or we overflow the line for some reason. 57 | if is_win32: 58 | spacing -= 1 59 | 60 | sys.stderr.write("\r{0}".format(msg)) 61 | sys.stderr.write(" " * max(0, spacing)) 62 | sys.stderr.flush() 63 | 64 | 65 | def format_filesize(size): 66 | """Formats the file size into a human readable format.""" 67 | for suffix in ("bytes", "KB", "MB", "GB", "TB"): 68 | if size < 1024.0: 69 | if suffix in ("GB", "TB"): 70 | return "{0:3.2f} {1}".format(size, suffix) 71 | else: 72 | return "{0:3.1f} {1}".format(size, suffix) 73 | 74 | size /= 1024.0 75 | 76 | 77 | def format_time(elapsed): 78 | """Formats elapsed seconds into a human readable format.""" 79 | hours = int(elapsed / (60 * 60)) 80 | minutes = int((elapsed % (60 * 60)) / 60) 81 | seconds = int(elapsed % 60) 82 | 83 | rval = "" 84 | if hours: 85 | rval += "{0}h".format(hours) 86 | 87 | if elapsed > 60: 88 | rval += "{0}m".format(minutes) 89 | 90 | rval += "{0}s".format(seconds) 91 | return rval 92 | 93 | 94 | def create_status_line(**params): 95 | """Creates a status line with appropriate size.""" 96 | max_size = get_terminal_size().columns - 1 97 | 98 | for fmt in PROGRESS_FORMATS: 99 | status = fmt.format(**params) 100 | 101 | if len(status) <= max_size: 102 | break 103 | 104 | return status 105 | 106 | 107 | def progress(iterator, prefix): 108 | """Progress an iterator and updates a pretty status line to the terminal. 109 | 110 | The status line contains: 111 | - Amount of data read from the iterator 112 | - Time elapsed 113 | - Average speed, based on the last few seconds. 114 | """ 115 | if terminal_width(prefix) > 52: 116 | prefix = (get_cut_prefix(prefix, 50)) 117 | speed_updated = start = time() 118 | speed_written = written = 0 119 | speed_history = deque(maxlen=5) 120 | 121 | for data in iterator: 122 | yield data 123 | 124 | now = time() 125 | elapsed = now - start 126 | written += len(data) 127 | 128 | speed_elapsed = now - speed_updated 129 | if speed_elapsed >= 0.5: 130 | speed_history.appendleft(( 131 | written - speed_written, 132 | speed_updated, 133 | )) 134 | speed_updated = now 135 | speed_written = written 136 | 137 | speed_history_written = sum(h[0] for h in speed_history) 138 | speed_history_elapsed = now - speed_history[-1][1] 139 | speed = speed_history_written / speed_history_elapsed 140 | 141 | status = create_status_line( 142 | prefix=prefix, 143 | written=format_filesize(written), 144 | elapsed=format_time(elapsed), 145 | speed=format_filesize(speed) 146 | ) 147 | print_inplace(status) 148 | sys.stderr.write("\n") 149 | sys.stderr.flush() 150 | -------------------------------------------------------------------------------- /cam4_mm.py: -------------------------------------------------------------------------------- 1 | # Cam4 Streamlink FFMPEG/STREAMLINK/LIVESTREAM/YOUTUBE-DL Plugin v.2.0.0 by @horacio9a for Python 2.7.18 2 | # coding: utf-8 3 | 4 | import os, sys, re, time, command 5 | 6 | reload(sys) 7 | sys.setdefaultencoding('utf-8') 8 | from streamlink.plugin import Plugin 9 | from streamlink.plugin.api import validate 10 | from streamlink.stream import HLSStream 11 | from datetime import datetime 12 | from colorama import init, Fore, Back, Style 13 | from termcolor import colored 14 | import ConfigParser 15 | Config = ConfigParser.ConfigParser() 16 | Config.read('config.ini') 17 | 18 | init() 19 | 20 | STREAM_INFO = "https://www.cam4.com/rest/v1.0/profile/{0}/streamInfo" 21 | INFO_URL = "https://www.cam4.com/rest/v1.0/search/performer/{0}" 22 | PROFILE_URL = "https://www.cam4.com/rest/v1.0/profile/{0}/info" 23 | 24 | _url_re = re.compile(r"https?://(\w+\.)?cam4\.com/(?P\w+)") 25 | 26 | class Cam4(Plugin): 27 | @classmethod 28 | def can_handle_url(cls, url): 29 | return _url_re.match(url) 30 | 31 | def _get_streams(self): 32 | match = _url_re.match(self.url) 33 | username = match.group("username") 34 | 35 | res = self.session.http.get(INFO_URL.format(username)) 36 | data = self.session.http.json(res) 37 | print 38 | self.logger.info("Username: {0}".format(data["username"])) 39 | name = data["username"] 40 | online = data["online"] 41 | self.logger.info("Online: {0}".format(data["online"])) 42 | self.logger.info("Country: {0}".format(data["country"])) 43 | res = self.session.http.get(PROFILE_URL.format(username)) 44 | data = self.session.http.json(res) 45 | self.logger.info("City: {0}".format(data["city"])) 46 | self.logger.info("Sex Preference: {0}".format(data["sexPreference"])) 47 | self.logger.info("Ethnicity: {0}".format(data["ethnicity"])) 48 | self.logger.info("Main Language: {0}".format(data["mainLanguage"])) 49 | self.logger.info("Breast Size: {0}".format(data["breastSize"])) 50 | self.logger.info("Birthdate: {0}".format(data["birthdate"])) 51 | self.logger.info("Age: {0}".format(int((datetime.now() - datetime.strptime(data["birthdate"], "%Y-%m-%d")).days / 365))) 52 | 53 | if online: 54 | res = self.session.http.get(STREAM_INFO.format(username)) 55 | data = self.session.http.json(res) 56 | if data["canUseCDN"]: 57 | hlsurl = data["cdnURL"] 58 | while True: 59 | try: 60 | print 61 | mode = int(raw_input(colored(" => MODE => EXIT(6) => URL(5) => YTDL(4) => LS(3) => SL(2) => FFMPEG(1) => PLAYER(0) => ", "white", "on_blue"))) 62 | break 63 | except ValueError: 64 | print(colored("\n => Input must be a number <=", "white", "on_red")) 65 | if mode == 0: 66 | mod = 'PLAYER' 67 | if mode == 1: 68 | mod = 'FFMPEG' 69 | if mode == 2: 70 | mod = 'SL' 71 | if mode == 3: 72 | mod = 'LS' 73 | if mode == 4: 74 | mod = 'YTDL' 75 | if mode == 5: 76 | mod = 'URL' 77 | if mode == 6: 78 | mod = 'EXIT' 79 | 80 | timestamp = str(time.strftime("%d%m%Y-%H%M%S")) 81 | stime = str(time.strftime("%H:%M:%S")) 82 | path = Config.get('folders', 'output_folder_C4') 83 | fn = name + '_C4_' + timestamp 84 | fn1 = name + '_C4_' + timestamp + '.flv' 85 | fn2 = name + '_C4_' + timestamp + '.mp4' 86 | fn3 = name + '_C4_' + timestamp + '.ts' 87 | fn4 = name + '_C4_' + timestamp + '.txt' 88 | pf1 = (path + fn1) 89 | pf2 = (path + fn2) 90 | pf3 = (path + fn3) 91 | pf4 = (path + fn4) 92 | player = Config.get('files', 'player') 93 | ffmpeg = Config.get('files', 'ffmpeg') 94 | streamlink = Config.get('files', 'streamlink') 95 | livestreamer = Config.get('files', 'livestreamer') 96 | youtube = Config.get('files', 'youtube') 97 | 98 | if mod == 'PLAYER': 99 | print 100 | print (colored(' => PLAYER => {} <=', 'white', 'on_magenta')).format(fn) 101 | print 102 | command = ('{} -p {} hlsvariant://{} best'.format(livestreamer, player, hlsurl)) 103 | os.system(command) 104 | while True: 105 | try: 106 | print 107 | prog = int(raw_input(colored(' => Mode => URL(5) - YTDL(4) - LS(3) - SL(2) - FFMPEG(1) - Exit(0) => ', 'white', 'on_green'))) 108 | break 109 | except ValueError: 110 | print 111 | print(colored(' => Input must be a number <=', 'white', 'on_red')) 112 | if prog > 5: 113 | print 114 | print(colored(' => Too big number <=', 'white', 'on_red')) 115 | mod = 'EXIT' 116 | if prog == 0: 117 | mod = 'EXIT' 118 | if prog == 1: 119 | mod = 'FFMPEG' 120 | if prog == 2: 121 | mod = 'SL' 122 | if prog == 3: 123 | mod = 'LS' 124 | if prog == 4: 125 | mod = 'YTDL' 126 | if prog == 5: 127 | mod = 'URL' 128 | 129 | if mod == 'FFMPEG': 130 | print (colored("\n => FFMPEG-REC => {} <=","white","on_red")).format(fn1) 131 | print 132 | command = ('{} -hide_banner -loglevel panic -i {} -c:v copy -c:a aac -b:a 160k {}'.format(ffmpeg,hlsurl,pf1)) 133 | os.system(command) 134 | 135 | if mod == 'SL': 136 | print 137 | print (colored(' => SL-REC => {} ( Size @ Speed ) <=', 'white', 'on_red')).format(fn2) 138 | print 139 | command = ('{} hls://{} best -Q --hls-live-edge 1 --hls-playlist-reload-attempts 9 --hls-segment-threads 3 --hls-segment-timeout 5.0 --hls-timeout 20.0 -o {}'.format(streamlink,hlsurl,pf2)) 140 | os.system(command) 141 | 142 | if mod == 'LS': 143 | print 144 | print (colored(' => LS-REC => {} ( Size @ Speed ) <=', 'white', 'on_red')).format(fn2) 145 | print 146 | command = ('{} hlsvariant://{} best -Q -o {}'.format(livestreamer,hlsurl,pf2)) 147 | os.system(command) 148 | 149 | if mod == 'YTDL': 150 | print 151 | print (colored(' => YTDL-REC => {} <=', 'white', 'on_red')).format(fn3) 152 | command = ('{} -i --geo-bypass --hls-use-mpegts --no-part -q --no-warnings --no-check-certificate {} -o {}'.format(youtube,hlsurl,pf3)) 153 | os.system(command) 154 | 155 | if mod == 'URL': 156 | print 157 | print (colored(' => URL => {} <=', 'white', 'on_green')).format(fn4) 158 | file=open(pf4,'w') 159 | file.write(hlsurl) 160 | file.close() 161 | 162 | else: 163 | print 164 | print(colored(' => Performer is in PRIVATE SHOW <=',"white","on_red")) 165 | print 166 | print(colored(' => END <= ', 'white','on_blue')) 167 | time.sleep(3) 168 | sys.exit() 169 | 170 | else: 171 | print 172 | print(colored(' => Performer is OFFLINE <=', 'white','on_red')) 173 | print 174 | print(colored(' => END <=', 'white','on_blue')) 175 | time.sleep(3) 176 | sys.exit() 177 | 178 | __plugin__ = Cam4 --------------------------------------------------------------------------------