IPTV 四季線上 4GTV 解析播放代码 第一弹

coding · 2024年05月13日 · 最后由 coding 回复于 2024年05月15日 · 3955 次阅读
本帖已被管理员设置为精华贴

4gtv 在台湾地区也属于比较流行的流媒体平台,它的频道数量是非常多的,正常情况下,普通用户只能观看免费频道以及 720P 的直播,但实际上它的系统代码是比较简单的,下面的方法可以帮助你解析出 1080P 的直播地址,以及付费频道的直播地址。

首先按照我的理解,我将 4gtv 的频道分为三类:

  1. 使用数字 ID
  2. 使用字符 ID

了解 Livednow 的朋友应该知道,之前我们提供港澳台的频道直播,但是后面因为一些原因暂停了,现在重新整理代码的时候发现之前使用的方法已经失效,所以本贴先暂时分享其中一种有效的方法,等我后续再发布其他方式。

本方法无需绕过 Cloudflare WAF 验证,但是有些代理 IP 无法解锁,具体需要自己测试

代码

这类频道的 ID 类似 民視第一台 对应的 1,以下是代码:

import json
from urllib.parse import urlparse, parse_qs

from utils import my_requester, encrypt_data, decrypt_data


def get_channel(channel_id):
    channel_info = {
        'id': channel_id,
    }
    get_channel_api = f'https://api2.4gtv.tv/Channel/GetChannel/{channel_id}'
    headers = {
        'User-Agent': 'okhttp/3.12.11'
    }
    response = my_requester('GET', url=get_channel_api, headers=headers)
    if not response:
        print('Failed to get channel info')
        return channel_info

    response_dict = response.json()
    channel_info['name'] = response_dict['Data']['fsNAME']
    request_dict = {
        'fnCHANNEL_ID': response_dict['Data']['fnID'],
        'fsASSET_ID': response_dict['Data']['fs4GTV_ID'],
        'fsDEVICE_TYPE': 'mobile',
        'clsIDENTITY_VALIDATE_ARUS': {'fsVALUE': ''}
    }
    encrypt = encrypt_data(json.dumps(request_dict))
    request_data = {'value': encrypt.decode()}
    headers['Content-Type'] = 'application/x-www-form-urlencoded'
    response = my_requester('POST',
                            url='https://api2.4gtv.tv/Channel/GetChannelUrl3',
                            data=request_data, headers=headers)
    if not response:
        print('Failed to get channel play info')
        return channel_info

    response_dict = response.json()
    if 'Data' not in response_dict:
        print('Failed to get channel play info')
        return channel_info

    decrypt = decrypt_data(response_dict['Data'])
    playlist = json.loads(decrypt)
    play_url = playlist['flstURLs'][0]
    if play_url.startswith('https://4gtvfree-cds.cdn.hinet.net'):
        play_url = play_url.replace('/index.m3u8?', '/1080.m3u8?')
    else:
        play_url = get_play_raw(play_url, return_type='url')

    channel_info['play_url'] = play_url
    return channel_info


def get_play_raw(url, return_type='raw'):
    headers = {
        'User-Agent': 'okhttp/3.12.11'
    }
    response = my_requester('GET', url=url, headers=headers)
    parsed_url = urlparse(url)
    if not response:
        return None

    resp_text = response.text.strip()
    latest_line = resp_text.split('\n')[-1]
    url_path = '/'.join(parsed_url.path.split('/')[0:-1])
    new_url = f'{parsed_url.scheme}://{parsed_url.netloc}{url_path}/{latest_line}'

    if return_type == 'url':
        return new_url

    if '.ts' in latest_line:
        return resp_text
    else:
        return get_play_raw(new_url, return_type)


def play_4gtv(play_url):
    m3u8_raw = get_play_raw(play_url)
    if not m3u8_raw:
        print('Failed to get play url')

    if play_url.startswith('https://4gtvfree-cds.cdn.hinet.net'):
        # live 格式的处理
        base_url = play_url.split('?')[0].rsplit('/', 1)[0]
        lines = []
        for line in m3u8_raw.split('\n'):
            if line.startswith('#EX') or not line.strip():
                lines.append(line)
            elif '.ts' in line:
                ts_file = line.split('?')[0]
                parsed_url = urlparse(line)
                params = parse_qs(parsed_url.query)
                params_simplified = {k: v[0] for k, v in params.items()}

                ts_url = (f'{base_url}/{ts_file}?'
                          f'token1={params_simplified["token1"]}'
                          f'&expires1={params_simplified["expires1"]}')
                lines.append(ts_url)
            else:
                lines.append(line)
        m3u8_raw = '\n'.join(lines)

    else:
        channel = play_url.split('?')[0].split('/')[-3]
        lines = []
        prex = f'https://litvpc-hichannel.cdn.hinet.net/live/pool/{channel}/litv-pc/'
        for line in m3u8_raw.split('\n'):
            if line.startswith('#EXT') or not line.strip():
                lines.append(line)
            else:
                ts_file = line.split('?')[0]
                ts_file = ts_file.replace('video=2000000', 'video=6000000')
                ts_file = ts_file.replace('video=2936000', 'video=5936000')
                ts_file = ts_file.replace('video=3000000', 'video=6000000')
                ts_file = ts_file.replace('avc1_2000000=3', 'avc1_6000000=1')
                ts_file = ts_file.replace('avc1_2000000=6', 'avc1_6000000=1')
                ts_file = ts_file.replace('avc1_2936000=4', 'avc1_6000000=5')
                ts_file = ts_file.replace('avc1_3000000=3', 'avc1_6000000=1')
                ts_url = f'{prex}{ts_file}'
                lines.append(ts_url)

        m3u8_raw = '\n'.join(lines)
    print(m3u8_raw)


if __name__ == '__main__':
    info = get_channel('1')
    print(info)
    if 'play_url' in info:
        play_4gtv(info['play_url'])

Docker 运行

以上代码仅提供一些思路和测试方法,如果要在播放器中播放,可以使用我已经构建好的 Docker 镜像 pixman/4gtv:

# run
docker run --name=4gtv -d -p 5000:5000 pixman/4gtv

# 使用代理 (192.168.1.1:7890 替换成你的代理地址)
docker run --name=4gtv -d -p 5000:5000 -e HTTP_PROXY=http://192.168.1.1:7890 -e HTTPS_PROXY=http://192.168.1.1:7890 pixman/4gtv

# test
curl http://localhost:5000/4gtv/1 --location

在播放器中使用 http://localhost:5000/4gtv/{ID} 即可播放。

注意事项

关于代理,只需将以下域名添加到你的规则中即可:

  • api2.4gtv.tv
  • 4gtvfreemobile-cds.cdn.hinet.net
  • 4gtvfree-cds.cdn.hinet.net

如果你在请求时,报错 ERROR in utils: Request failed: 403 Client Error: Forbidden for url: https://api2.4gtv.tv/Channel/GetChannelUrl3,说明你的 IP 无法解锁,可以尝试使用其他代理。

附录

utils.py

import base64
import time
import logging

import requests
import urllib3
from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import pad, unpad

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

KEY = 'ilyB29ZdruuQjC45JhBBR7o2Z8WJ26Vg'.encode()
IV = 'JUMxvVMmszqUTeKn'.encode()


def my_requester(method, **kwargs):
    retry_times = 3
    timeout = 5
    if 'timeout' in kwargs:
        timeout = kwargs.pop('timeout', 5)
    verify = False
    if 'verify' in kwargs:
        verify = kwargs.pop('verify', False)

    while retry_times > 0:
        try:
            resp = requests.request(method, timeout=timeout, verify=verify, **kwargs)
            resp.raise_for_status()
            return resp
        except Exception as e:
            retry_times -= 1
            time.sleep(0.5)
            logging.error(f'Request failed: {e}, retrying...')

    logging.error('Request failed after retrying')
    return None


def encrypt_data(data):
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    padded_data = pad(data.encode(), AES.block_size)
    return base64.b64encode(cipher.encrypt(padded_data))


def decrypt_data(encrypted_data):
    decipher = AES.new(KEY, AES.MODE_CBC, IV)
    decrypted_data = unpad(decipher.decrypt(base64.b64decode(encrypted_data)), AES.block_size)
    return decrypted_data.decode()

可用 ID 列表

id name
1 民視第一台
2 民視台灣台
3 民視
4 中視
6 華視
7 公視戲劇
8 好消息 2 台
9 好消息
11 達文西頻道
15 靖天卡通台
16 民視綜藝台
19 愛爾達娛樂台
21 靖天綜合台
22 靖天日本台
23 龍華戲劇台
24 民視影劇台
25 采昌影劇台
28 龍華日韓台
30 中天新聞台
31 民視新聞台
33 中視新聞
34 華視新聞
36 寰宇新聞台
38 視納華仁紀實頻道
39 amc 電影台
40 影迷數位電影台
42 靖天映畫
48 博斯高球台
50 博斯運動一台
51 博斯無限台
52 博斯網球台
57 TRACE Sport Stars
58 智林體育台
59 靖洋卡通 Nice Bingo
60 i-Fun 動漫台
61 民視旅遊台
69 時尚運動 X
78 TRACE Urban
79 Mezzo Live HD
80 CLASSICA 古典樂
82 靖天電影台
83 靖天育樂台
84 KLT-靖天國際台
85 半島國際新聞台
86 VOA 美國之音
88 靖天資訊台
93 鳳梨直擊台
94 香蕉直擊台
106 ELTV 生活英語台
107 客家電視台
113 豬哥亮歌廳秀
114 八大綜藝台
116 靖天戲劇台
118 靖洋戲劇台
119 Nice TV 靖天歡樂台
121 龍華電影台
123 中視菁采台
124 中視經典台
139 Love Nature
160 車迷 TV
168 幸福空間居家台
169 三立綜合台
170 國會頻道 1
171 國會頻道 2
172 八大精彩台
173 東森購物一台
174 東森購物二台
175 LUXE TV Channel
176 My Cinema Europe HD 我的歐洲電影
178 TV5MONDE STYLE HD 生活時尚
179 GINX Esports TV
180 ROCK Action
181 TechStorm
182 Pet Club TV
183 TVBS
184 TVBS 歡樂台
185 尼克兒童頻道
186 HITS 頻道
188 LiveABC 互動英語頻道
189 ARIRANG 阿里郎頻道
201 經典電影台
202 經典卡通台
204 精選動漫台
209 大愛電視
210 人間衛視
212 影迷數位紀實台
213 MTV Live HD 音樂頻道
214 History 歷史頻道
215 CI 罪案偵查頻道
217 Lifetime 娛樂頻道
218 電影原聲台 CMusic
219 Nick Jr. 兒童頻道
223 XTR 亞太台
224 SBN 全球財經台
225 CinemaWorld
226 DW 德國之聲
227 TVBS 精采台
229 三立新聞 iNEWS
230 大愛二台
231 MOMO 親子台
235 CNBC Asia 財經台
236 金光布袋戲
237 愛爾達生活旅遊台
244 戲劇免費看 2 台
245 電影免費看 2 台
246 民視
249 滾動力 rollor
250 亞洲旅遊台
252 Global Trekker
254 民視第一台
255 民視台灣台
256 華視新聞
257 民視新聞台
258 中視新聞
260 中天新聞台
261 亞洲旅遊台
268 鏡電視新聞台
269 兒童卡通台
270 戲劇免費看 1 台
273 原住民族電視台
274 fun 探索娛樂台
275 ROCK Entertainment
277 時尚運動 X
278 超人力霸王整套看
279 花系列 經典劇場
280 寰宇新聞台灣台
281 寰宇財經台
282 DreamWorks 夢工廠動畫
283 Bloomberg TV
284 TVBS 綜藝台
285 TVBS 台劇台
286 東森購物四台
287 東森購物三台
288 Globetrotter 環遊旅行家
289 番茄直擊台
290 芭樂直擊台
291 TVBS 新聞
292 東森新聞台
293 東森財經新聞台
coding 将本帖设为了精华贴。 05月13日 00:43

哈哈哈哈

老规矩,一键三连

一键三连😈

挺不错的,可惜要台湾原生 ip,学习学习

Jordankobe 回复

大部分机场其实都行的

一键三连

好详细,先收慢慢消化

一键三连

感谢楼主分享精品,可以看

一键三连

一键三连

支持 Coding

太强了,回帖膜拜!

小白也可以看 4Gtv,感视大神!!

感谢分享

收藏收藏

太棒了,手工点赞!

楼主,希望 docker 可以支援 socks 代理

bobotoy 回复

可以的,你启动的时候设置

docker run --name=4gtv -d -p 5000:5000 -e HTTP_PROXY=socks5://192.168.1.1:7891 -e HTTPS_PROXY=socks5://192.168.1.1:7891 pixman/4gtv

这个容器怎么安装使用呢?自己有谷歌云的 vps,可以安装使用吗?

谢谢分享谢谢分享谢谢分享

还是有门槛的,openwrt,使用 openclash 代理的,使用 docker 搭建了,打不开,提示{"error":"Failed to get channel info"}。看不到容器 log

容器搭建很方便,一次过,第三步 test(curl http://localhost:5000/4gtv/1 --location),提示{"error":"Failed to get channel info"}。是节点的问题吗?

ohjust 回复

可以的,但是我不确定谷歌 vps 能不能解锁

ohjust 回复

是的,换个 ip 试试

bzkwbeek8 回复

可能是 oc 没有代理本机,试试下面的方法

# 使用代理 (192.168.1.1:7890 替换成你的代理地址)
docker run --name=4gtv -d -p 5000:5000 -e HTTP_PROXY=http://192.168.1.1:7890 -e HTTPS_PROXY=http://192.168.1.1:7890 pixman/4gtv

分享才是快乐

coding 回复

ERROR in utils: Request failed: Missing dependencies for SOCKS support., retrying...

群主一键部署命令后面加个参数,要不重启服务器不会自动启动,(--restart)。或者部署完了的用这个命令 执行一遍(docker container update --restart=always 容器名字)

35 楼 已删除

{"error":"Failed to get play url"} 获取播放网址失败

await 回复

可以的,根据你需要来

linx 回复

可能是 ip 不太行,换个区域试试

coding 回复

容器部署在 vpa,那就不能用代理了吧。

ohjust 回复

不需要

bzkwbeek8 回复

请问解决了 openclash 不能播放的问题吗?我用 openclash 连 4gtv 的网站都打不开,ShadowSocksR Plus+ 就可以正常运行 docker。

jason 回复

我用 ShadowSocksR Plus+ ,网页可以播放,docker 还是不成功。还是提示"error":"Failed to get channel info"}

coding 回复

试过代理,还是提示"error":"Failed to get channel info",不知道什么原因。 stderr: [2024-05-15 01:01:19,816] ERROR in utils: Request failed: HTTPSConnectionPool(host='api2.4gtv.tv', port=443): Max retries exceeded with url: /Channel/GetChannel/19 (Caused by ProxyError('Unable to connect to proxy', NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused'))), retrying...

44 楼 已删除
bzkwbeek8 回复

我和你一样,同样环境,也是打不开。

真心不知怎樣可以在 aptv,Tivimate 或 Ott app 上應用?😅

coding 四季線上 4GTV 无需解锁代理播放 第二弹 提及了此话题。 05月15日 16:36
BreadnButter 回复

等我出个 m3u 文件

coding Docker 镜像 pixman/pixman 使用说明 提及了此话题。 05月21日 15:32
需要 登录 后方可回复, 如果你还没有账号请 注册新账号