├── 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 | 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 | 6 | 7 | -------------------------------------------------------------------------------- /scripts/.idea/YT2Plex.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 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 | --------------------------------------------------------------------------------