├── scripts
├── .idea
│ ├── .name
│ ├── .gitignore
│ ├── vcs.xml
│ ├── inspectionProfiles
│ │ └── profiles_settings.xml
│ ├── modules.xml
│ ├── misc.xml
│ └── YT2Plex.iml
├── playlist.m3u
├── guide.xml
└── main.py
├── LICENSE
└── README.md
/scripts/.idea/.name:
--------------------------------------------------------------------------------
1 | main.py
--------------------------------------------------------------------------------
/scripts/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/scripts/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/scripts/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/scripts/playlist.m3u:
--------------------------------------------------------------------------------
1 | #EXTM3U
2 | #EXTINF:-1 tvg-id="UCeY0bbntWzzVIaj2z3QigXg" tvg-name="NBC" tvg-logo="https://yt3.ggpht.com/GjDLYFGF4IQaUobUK-6q3nOsU4o8fRMl4XgVipPWRqdRVt61s2LqgnbBXu3-qYL4Ab2xsfVo=s176-c-k-c0x00ffffff-no-rj-mo", NBC
3 | https://www.youtube.com/watch?v=NoQLU2uJij0
4 |
--------------------------------------------------------------------------------
/scripts/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/scripts/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/scripts/.idea/YT2Plex.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/scripts/guide.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | NBC
5 |
6 |
7 |
8 | NBC Live Stream
9 | Live stream from NBC on YouTube
10 |
11 |
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 borinbilly
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # YT2Plex
2 | Pull YouTube live streams into m3u playlist and xml guide for use with IPTV. Configured for use with xteve on an unraid system, but this can be changed by editing the directories in main.py
3 |
4 | # SETUP
5 |
6 | 1. Install xteve
7 |
8 | Can be found in community applications
9 |
10 | ENABLE FFMPEG
11 |
12 | Xteve -> Settings -> Streaming -> Stream Buffer: FFmpeg
13 |
14 | 3. Add scripts folder from YT2Plex to xsteve
15 |
16 | On unraid the directory where to add /scripts should look something like:
17 | /mnt/user/appdata/xteve
18 |
19 | 4. Edit main.py
20 | Add your API key to API_KEY
21 |
22 | Sign up for youtube data api at https://console.developers.google.com/apis
23 |
24 | API key can be generated under credentials tab
25 |
26 | Add/Remove Youtube channels you want
27 |
28 | Requirements:
29 |
30 | Youtube channel ID (obtain from https://www.streamweasels.com/tools/youtube-channel-id-and-user-id-convertor/)
31 |
32 | Channel Name (Name you want to appear in channel guide)
33 |
34 | Channel Icon (Link to image you want to appear in channel guide)
35 |
36 | 5. Install Python 3 on your system
37 |
38 | For Unraid systems:
39 |
40 | Install Nerd Tools from the community apps
41 |
42 | Open Nerd Tools
43 |
44 | Search for python
45 |
46 | Enable
47 |
48 | 7. Create a virtual enviroment for dependancies (Reccommended)
49 |
50 | In terminal:
51 |
52 | cd /path/to/your/directory
53 |
54 | pip3 install virtualenv
55 |
56 | virtualenv [Name of virtual env]
57 |
58 | source myenv/bin/activate
59 |
60 | 9. Install dependancies
61 |
62 | pip install google-api-python-client yt-dlp
63 |
64 | 11. Test
65 |
66 | Open Terminal
67 |
68 | /path/to/virtualenviroment/bin/python3 /path/to/xteve/scripts/main.py
69 |
70 | Verify playlist.m3u and guide.xml were created
71 |
72 | 11. Direct xteve to the new playlist and guide
73 |
74 | By default the xteve refers to its path as /root/.xteve so the path put into xteve should look like "/root/.xteve/scripts/playlist.m3u"
75 |
76 | 13. Map playlist and xmltv guide
77 |
78 | 14. Setup cronjob to run main.py however often you want to check the channels for live streams
79 |
80 | You may also want to set a job to refresh the guide in plex as the lowest refresh interval plex offers is 1hr
81 |
82 | Example command to refresh Plex guide:
83 |
84 | curl "http://127.0.0.1:32400/livetv/dvrs/4/reloadGuide?X-Plex-Token=" -X "POST"
85 |
86 | 16. Add xteve to Plex in Live TV / DVR settings and scan
87 |
--------------------------------------------------------------------------------
/scripts/main.py:
--------------------------------------------------------------------------------
1 | #!/mnt/user/appdata/xteve/scripts/yt2p/bin/python3
2 |
3 | import os
4 | import subprocess
5 | from googleapiclient.discovery import build
6 | from datetime import datetime, timedelta
7 |
8 | # Replace with your own API key
9 | API_KEY = 'INSERT API KEY HERE'
10 |
11 | # List of channels, Channel IDs via https://www.streamweasels.com/tools/youtube-channel-id-and-user-id-convertor/
12 | #icon should be links to images you want displayed in Plex for each channel
13 | channels = [
14 | {
15 | 'id': 'UCeY0bbntWzzVIaj2z3QigXg',
16 | 'name': 'NBC',
17 | 'icon': 'https://yt3.googleusercontent.com/Iyl3USdPKmYU1klQW1El44iCAsRZtfHobgBkIhdwm8sjgZXIfsVttGob8_cTXhU1rSWIMUEDaw=s900-c-k-c0x00ffffff-no-rj'
18 | },
19 | {
20 | 'id': 'UC52X5wxOL_s5yw0dQk7NtgA',
21 | 'name': 'AP',
22 | 'icon': 'https://yt3.googleusercontent.com/eYjjY5MUJ422vBuGFg--wNR1b093BaAFzJhbZYLhp8rye5gcwXyPQAtNz2j_4wXSf-Qc5J3UsA=s160-c-k-c0x00ffffff-no-rj'
23 | },
24 | {
25 | 'id': 'UCyUTC3jCAaLd639lo9n9Bqw',
26 | 'name': 'CAFE',
27 | 'icon': 'https://yt3.googleusercontent.com/kj9VngnbnhkmZrqHJPnxvbh7KaNkLp_UYu3UMI-7G1FoPsRqwObpgRWAhi1gbZ4_vEIW15Yuhw=s160-c-k-c0x00ffffff-no-rj'
28 | },
29 | # Add more channels here
30 | # {
31 | # 'id': 'CHANNEL_ID_2',
32 | # 'name': 'CHANNEL_NAME_2',
33 | # 'icon': 'CHANNEL_ICON_2'
34 | # },
35 | ]
36 |
37 | # Build the YouTube API client
38 | youtube = build('youtube', 'v3', developerKey=API_KEY)
39 |
40 | output_dir = '/mnt/user/appdata/xteve/scripts/'
41 |
42 | # Create M3U and XMLTV file content placeholders
43 | m3u_content = "#EXTM3U\n"
44 | xmltv_content = '\n\n'
45 |
46 | for channel in channels:
47 | CHANNEL_ID = channel['id']
48 | CHANNEL_NAME = channel['name']
49 | CHANNEL_ICON = channel['icon']
50 |
51 | # Search for live broadcasts on the specified channel
52 | request = youtube.search().list(
53 | part='snippet',
54 | channelId=CHANNEL_ID,
55 | eventType='live',
56 | type='video'
57 | )
58 | response = request.execute()
59 |
60 | # Extract live stream URL from the response
61 | live_stream_url = None
62 | if 'items' in response and len(response['items']) > 0:
63 | live_stream_url = f"https://www.youtube.com/watch?v={response['items'][0]['id']['videoId']}"
64 |
65 | if live_stream_url:
66 | print(f"Live stream URL for {CHANNEL_NAME}: {live_stream_url}")
67 |
68 | # Fetch HLS stream URL using yt-dlp
69 | try:
70 | result = subprocess.run(
71 | ['/mnt/user/appdata/xteve/scripts/yt2p/bin/yt-dlp', '-g', live_stream_url],
72 | capture_output=True,
73 | text=True,
74 | check=True
75 | )
76 | hls_url = result.stdout.strip()
77 | print(f"HLS Stream URL for {CHANNEL_NAME}: {hls_url}")
78 |
79 | if hls_url:
80 | # Append to M3U file content
81 | m3u_content += f"""#EXTINF:-1 tvg-id="{CHANNEL_ID}" tvg-name="{CHANNEL_NAME}" tvg-logo="{CHANNEL_ICON}", {CHANNEL_NAME}
82 | {hls_url}
83 | """
84 |
85 | # Append to XMLTV file content
86 | current_time = datetime.utcnow()
87 | program_start = current_time.strftime('%Y%m%d%H%M%S +0000')
88 | program_end = (current_time + timedelta(hours=1)).strftime('%Y%m%d%H%M%S +0000') # Assuming a 1-hour program
89 |
90 | xmltv_content += f"""
91 | {CHANNEL_NAME}
92 |
93 |
94 |
95 | {CHANNEL_NAME} Live Stream
96 | Live stream from {CHANNEL_NAME} on YouTube
97 |
98 | """
99 | else:
100 | print(f"No valid HLS stream URL found for {CHANNEL_NAME}.")
101 | except subprocess.CalledProcessError as e:
102 | print(f"Failed to fetch HLS URL for {CHANNEL_NAME}: {e}")
103 | else:
104 | print(f"No live stream found for {CHANNEL_NAME}.")
105 |
106 | # Finalize XMLTV content
107 | xmltv_content += ''
108 |
109 | # Save M3U file
110 | m3u_path = os.path.join(output_dir, 'playlist.m3u')
111 | with open(m3u_path, 'w') as m3u_file:
112 | m3u_file.write(m3u_content)
113 | print(f"M3U file created: {m3u_path}")
114 |
115 | # Save XMLTV file
116 | xmltv_path = os.path.join(output_dir, 'guide.xml')
117 | with open(xmltv_path, 'w') as xmltv_file:
118 | xmltv_file.write(xmltv_content)
119 | print(f"XMLTV file created: {xmltv_path}")
120 |
121 |
--------------------------------------------------------------------------------