meeb / tubesync

Syncs YouTube channels and playlists to a locally hosted media server
GNU Affero General Public License v3.0
1.99k stars 130 forks source link

Saving as MP4 format #430

Open RtypeStudios opened 1 year ago

RtypeStudios commented 1 year ago

I've been trying to use tubesync (version 0.13.1) with Plex. The issues seems that Plex wont read metadata from MKV files. I was planning on writing a script that reads the file details out of the NFO (which plex also wont read). Then set the metadata on the mp4 file so that plex can ingest it.

Is there a way to set the output format? If not I'm happy to have a go at adding it.

MKV not read by plex:

meeb commented 1 year ago

The issue with using MP4 as a container is it doesn't, or didn't, support all the supported formats of the media streams from YouTube. Using MP4 restricts the codec choice and other options. MKV is the only container that supports all the available media streams as a container. It's possible to add, but you would need to also dynamically disable other format choices (and do a load of testing to find out what is and is not supported in the first place). It was easiest to hard code MKV containers.

It's possible to just select whatever input streams and then an MP4 output container with ffmpeg, but then you run the risk of doing actual transcoding (e.g. VP9 to x265 for the video stream or something) rather than just basic remuxing with no conversion which is what tubesync does at the moment and remuxing is of course massively preferable.

I use Plex myself and not reading some metadata from MKV files is indeed irritating.

RtypeStudios commented 1 year ago

Thank you for replying!

Oh interesting, I didn't know that. That does make it difficult and yeah of course you want to avoid the entire re-encoding.

I'm just trying to get the video titles into Plex. The videos show up grouped by year as the season, which is great. but each episode is named as a date. Have you managed to find a way to get the video title showing in plex?

I might try putting a script together that reads the NFO file and puts the episode information directly into Plex. Then if Plex dies you can just re-run the script again and re-import the information. Not perfect but it beats hand updating each video.

Thanks also for your great work on TubeSync and for embracing the self hosted ethos.

meeb commented 1 year ago

Honestly after many attempts ranging from basic up to advanced methods from specific file names up to scripts that spam data into Plex via the unofficial API I've ended up just having tubesync in its own Plex library and using the browse by folder option with media that has YYYY-MM-DD prefixes for sorting.

Hardly ideal, but it wasn't worth the fight in the end. There are existing metadata plugins for Plex that read NFO files that tubesync can optionally write, but Plex has made it a pain to manage the plugins now.

Jellyfin has better support from brief testing.

RtypeStudios commented 1 year ago

Ahh I thought there was an official API for Plex. Okay sounds like you have tried everything.

I tried Emby, which works. But I end up in the situation where Emby just jams everything in one season and sorts by the video title rather than the video date. I suspect I can fix that with a transform on the XML file. But i'd rather keep using Plex.

I will have a dig around and see if I can spot anything. but sounds like a lost cause.

meeb commented 1 year ago

I must admit I've not really looked into it for a while after I got annoyed with it last time. If you find something that works well feel free to share!

RtypeStudios commented 1 year ago

This seems to work okay. Updates a few hundred videos pretty quickly, It is just a proof of concept but it seems to work. I'm off to bed, but will improve on it tomorrow.

pip install plexapi pip install lxml

import os
from pathlib import Path
from plexapi.server import PlexServer
import lxml.etree as ET

baseurl = 'http://plex.lan:32400'
token = '<your token>'

plex = PlexServer(baseurl, token)

videos = plex.library.section('TubeSync')

for video in videos.search(unwatched=True):
    for part in video.iterParts():
        nfo_data_file_path = part.file.replace(".mkv", ".nfo")
        nfo_data_file_path = '/mnt' + nfo_data_file_path

        print(nfo_data_file_path)

        if os.path.exists(nfo_data_file_path):
            parser = ET.XMLParser(recover=True)
            tree = ET.parse(nfo_data_file_path, parser=parser)
            root = tree.getroot()
            video.editTitle(root.find('title').text, locked=True)
            video.editSortTitle(root.find('aired').text, locked=True)
tgouverneur commented 1 month ago

Reviving this... Thanks @RtypeStudios for this snippet, I actually made it into a repository: https://github.com/tgouverneur/tubesync-plex

I've been changing slightly to accomodate for a TV Shows library and not updating every episode eventually after the first run so I could run this in a crontab and update every new video TubeSync is downloading.

This seems to work best in order to avoid any XBMCnfo plugins and got rid of some headaches for me so... looks like it's going to be my final setup for now at least :)

Thanks!

RtypeStudios commented 1 month ago

Awesome! that's made my day. I'm really glad you could use the code.

I've since moved to Emby, will post up my Emby update script too. after I have given it a quick neatening up.

RtypeStudios commented 1 month ago

Here you go, it is a bit rough, but it seems to work :)

#!/usr/bin/python3

import os
from pathlib import Path
import lxml.etree as ET
import json
import requests
import urllib3

# Suppress only the single warning from urllib3.
urllib3.disable_warnings(category=urllib3.exceptions.InsecureRequestWarning)

# Server Address.
server_url = 'https://yourserver.lan'

# The Api key From Emby
key = ''

# Id of the user to do the updates as.
user_id = ''

library_id = ''
library_guid = ''

for t in requests.get(f'{server_url}/library/mediafolders?api_key={key}', verify=False).json()['Items']:

  if t['Name'] == 'TubeSync':
    library_id = t['Id']
    library_guid = t['Guid']
    break;

for show in requests.get(f'{server_url}/Items?IncludeItemTypes=Series&&ParentId={library_id}&Recursive=true&Limit=50&api_key={key}', verify=False).json()['Items']:

  print(f'{show["Name"]}');

  for season in requests.get(f'{server_url}/Shows/{show["Id"]}/Seasons?api_key={key}', verify=False).json()['Items']:

    print(f'\t{season["Name"]}');

    for episode in requests.get(f'{server_url}/Shows/{show["Id"]}/Episodes?SeasonId={season["Id"]}&api_key={key}', verify=False).json()['Items']:

      print(f'\t\tUpdating: {episode["Name"]} with id of \'{episode["Id"]}\'')

      episode_edit = requests.get(f'{server_url}/Users/{user_id}/Items/{episode["Id"]}?api_key={key}', verify=False).json()

      episode_edit["ParentIndexNumber"] = episode_edit['ProductionYear']

      episode_update = requests.post(f'{server_url}/Items/{episode["Id"]}?api_key={key}', json=episode_edit, verify=False)
meeb commented 1 month ago

Thanks for continuing this everyone! And thanks for the research and code snippets.