Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Cast file with local or remote subtitles #93

Merged
merged 17 commits into from
May 7, 2018

Conversation

marcosdiez
Copy link
Contributor

fixes #46

@skorokithakis
Copy link
Owner

Daaamn, this is amazing, thank you! @theychx, do you want to review this with me?

@theychx
Copy link
Collaborator

theychx commented May 3, 2018

Yep.

catt/cli.py Outdated
@@ -103,28 +105,98 @@ def write_config(settings):
raise CattCliError("No device specified.")


def hunt_subtitle(video):
dot_pos = video.rfind(".")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to use Pathlib here, and also to write a more robust algorithm. My subtitle names aren't always videofile.srt, sometimes they are videofile.en.srt and videofile.el.srt, so it would be good to do some globbing and fetch the first file alphabetically, if nothing is selected.

Copy link
Collaborator

@theychx theychx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works really well. I've left a few notes.

catt/cli.py Outdated
@cli.command(short_help="Send a video to a Chromecast for playing.")
@click.argument("video_url", callback=process_url)
@click.argument("subtitle", required=False, default=None, callback=process_subtitle)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default=None is not necessary, as the value becomes None if the argument is not supplied.

catt/cli.py Outdated
controller = "default" if force_default else None
cst, stream = setup_cast(settings["device"], video_url=video_url,
prep="app", controller=controller)

if stream.is_local_file:
click.echo("Casting local file %s..." % video_url)
click.echo("Playing %s on \"%s\"..." % (stream.video_title, cst.cc_name))
subtitle_url = load_subtitle_if_exists(subtitle, video_url, stream)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if you supplied port and local_ip to load_subtitle_if_exists instead of the entire stream object.

catt/cli.py Outdated
# it's an URL
return subtitle

if len(subtitle) > 4 and subtitle.lower()[-4:] == ".srt":
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if subtitle.lower().endswith(".srt") would be more concise.

catt/cli.py Outdated

def load_subtitle_if_exists(subtitle, video, stream):
if subtitle is None:
subtitle = hunt_subtitle(video)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subtitle = subtitle or hunt_subtitle(video) would be more concise.

catt/cli.py Outdated

if "://" in subtitle:
# it's an URL
return subtitle
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're not converting remote srt subtitles.

@marcosdiez
Copy link
Contributor Author

Hi! Thanks for the nice review. I agree 100% with all the mentioned items.
I am not really sure if anybody will ever try to load a remote subtitle, but converting it to WEBVTT was not hard anyway.

Congratulations once more for catt. It's really useful.

@marcosdiez
Copy link
Contributor Author

Sorry, I was not clear in my last message. I implemented everything you mentioned and also added an extra parameter --dont-load-subtitles-automatically, which could sometimes (readonly) be necessary

catt/cli.py Outdated
continue
if entry_path.stem.lower().startswith(video_path_stem_lower) and \
entry_path.suffix.lower() in [".vtt", ".srt"]:
return entry
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not working, as you're not returning the absolute path. You could do this:
for entry_path in video_path.parent.iterdir()
(and thus, no longer need os)
And then let the function return str(entry_path.resolve())

catt/cli.py Outdated
controller = "default" if force_default else None
cst, stream = setup_cast(settings["device"], video_url=video_url,
prep="app", controller=controller)

if stream.is_local_file:
click.echo("Casting local file %s..." % video_url)
click.echo("Playing %s on \"%s\"..." % (stream.video_title, cst.cc_name))
subtitle_url = load_subtitle_if_exists(subtitle, dont_load_subtitles_automatically, video_url, stream.local_ip, stream.port + 1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if the flag-handling logic was here, instead of the flag being passed on to the function. Like:

if subtitle is None and dont_load_subtitles_automatically:
    subtitle_url = None
else:
    subtitle_url = load_subtitle_if_exists(subtitle, video_url, stream.local_ip, stream.port + 1)

@marcosdiez
Copy link
Contributor Author

Sorry for the delay.
I was annoyed catt was not working on WSL and it took me a while to fix that.
It turns out the problem was in python-zeroconf ( python-zeroconf/python-zeroconf#130 ) so as soon as that get merged into pychromecast I'll be back here and ask you for another release :)

@marcosdiez
Copy link
Contributor Author

By the way, @theychx I don't know what's wrong with travis and flake8 today... it's not my fault. sorry for that. I had the same issue on the python zeroconf PR :(

@skorokithakis
Copy link
Owner

I've had issues with them being flaky as well. Usually just rerunning the test works.

@marcosdiez
Copy link
Contributor Author

@skorokithakis I rebased to make sure travis would run again and it failed again. My other PR also failed again, for the same reason...

any ideas ?

@theychx
Copy link
Collaborator

theychx commented May 5, 2018

@marcosdiez The travis fails do not seem to be caused by anything in your code. I have no idea whats going on.

Copy link
Collaborator

@theychx theychx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcosdiez This is really cool! Thanks!

@skorokithakis
Copy link
Owner

I have fixed the Travis failure. Let me see if I can update this branch with the fix.

@skorokithakis skorokithakis changed the title we can now cast localfile with local or remote subtitles feat: Cast local file with local or remote subtitles May 5, 2018
catt/cli.py Outdated
video_path = Path(video)
video_path_stem_lower = video_path.stem.lower()
for entry in video_path.parent.iterdir():
entry_path = Path(entry)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? Aren't the returned objects paths already?

catt/cli.py Outdated


def convert_srt_to_webvtt_helper(content):
content = re.sub(r'([\d]+)\,([\d]+)', r'\1.\2', content)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit too general, it will catch things like internationalized numbers in the subtitles and mistakenly convert them. Can we make it more specific?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, if there aren't more SRT formats:

# Find all lines that contain `-->` (timing lines) and replace commas in them with periods.
re.sub(r"^(.*? \-\-\> .*?)$", lambda m: m.group(1).replace(",", "."), content, flags=re.MULTILINE)

catt/cli.py Outdated


def convert_srt_to_webvtt(filename):
# print("Converting {} to WebVTT".format(filename))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment here should be removed.

catt/cli.py Outdated

def convert_srt_to_webvtt(filename):
# print("Converting {} to WebVTT".format(filename))
for possible_encoding in ['utf-8', 'iso-8859-15']:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use chardet here later on.

catt/cli.py Outdated


def load_subtitle_if_exists(subtitle, video, local_ip, port):
subtitle = subtitle or hunt_subtitle(video)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to change this again, but the ternary if is more readable here:

subtitle = subtitle if subtitle else hunt_subtitle(video)

return subtitle

if subtitle.lower().endswith(".srt"):
subtitle = convert_srt_to_webvtt(subtitle)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this give an error unless the extension is .srt or .vtt instead? We wouldn't want to continue with any arbitrary file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to https://developers.google.com/cast/docs/media , Chromecast supports:

TTML - Timed Text Markup Language

(example, https://gist.github.com/anotherhale/676a72edc84ca3a37c0c )
WebVTT - Web Video Text Tracks
(example: https://github.com/brenopolanski/html5-video-webvtt-example/blob/master/MIB2-subtitles-pt-BR.vtt
Line 21 Data Services
example: https://en.wikipedia.org/wiki/EIA-608

I confess I never heard about any of these 3 and I bet nobody will ever use cast with the latter. And XML is a bitch extension, so I think we are fine doing what the user tells us to instead of complaining.

What do you think ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that your code is not failing if the specified subtitle file has neither an ".srt" or ".vtt" extension.
(you are right that we should not concern ourselves with the other two obscure formats)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is there may be somebody who would like to use one of the obscure formats and if we arbitrarily forbid it because the extensions we are restricting for restriction's sake.

In the end of the day there is no downside in sending the wrong file to chromecast as an extension, so I think we should not restrict it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we don't check the extension for the main files we cast, so we should not bother with the subtitles either.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, okay, this makes sense to me, thanks.

Copy link
Owner

@skorokithakis skorokithakis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost done, I just have a few comments and then we're good to merge!

catt/cli.py Outdated
@cli.command(short_help="Send a video to a Chromecast for playing.")
@click.argument("video_url", callback=process_url)
@click.argument("subtitle", required=False, callback=process_subtitle)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be better served as an option, rather than as an argument.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but that would also change the applicability of --dont-load-subtitles-automatically. Because if this was an option, you might as well just say that --subtitle without any argument means "try to automatically load subtitle".

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly. And --no-subtitle would mean "don't load, even automatically".

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe NOT specifying --subtitle would suffice 😃 (I'll butt out of your review now. Sorry).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that there are three states, file/auto/none. So we need --subtitle/nothing/--no-subtitle.

Copy link
Collaborator

@theychx theychx May 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not specifying --subtitle means none.
Specifying --subtitle without an argument means auto.
Specifying --subtitle <subtitle> means file.
What am I missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if auto loading subtitles was the default option. That's why I wrote this way.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We both agree with that, we're just discussing the interface, basically. It's a bit tricky. Maybe --subs/--no-subs and it automatically discovers if you specify neither?

Copy link
Collaborator

@theychx theychx May 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So:
Nothing specified means "auto".
Specifying --subs <subtitle-file> means "file".
Specifying --no-subs means "none".

Having given this a bit of thought, I can see that this is probably the behaviour most users would expect.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good, we all agreed on the same thing. I just got somebody visiting me, so I'll continue later.

catt/cli.py Outdated
@click.option("-f", "--force-default", is_flag=True,
help="Force use of the default Chromecast app (use if a custom app doesn't work).")
@click.option("-r", "--random-play", is_flag=True,
help="Play random item from playlist, if applicable.")
@click.option("--dont-load-subtitles-automatically", is_flag=True, default=False,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be renamed to --no-subs? It's kind of long right now.

catt/cli.py Outdated
@click.pass_obj
def cast(settings, video_url, force_default, random_play):
def cast(settings, video_url, subtitle, force_default, random_play, dont_load_subtitles_automatically):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having subtitle be an option would work better here as well, as I don't like changing function signatures that drastically. However, we don't have default arguments for this, so it'd break anyway.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I misread "subtitle without any argument". I think loading the associated sub should be the default behaviour, without needing an argument...

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I.e. without needing to specify --subtitle, I mean.

@skorokithakis
Copy link
Owner

Thanks again for this PR, @marcosdiez, this is extremely useful. I'll release 0.7.0 as soon as we merge.

@skorokithakis skorokithakis changed the title feat: Cast local file with local or remote subtitles feat: Cast file with local or remote subtitles May 6, 2018
catt/cli.py Outdated
controller = "default" if force_default else None
cst, stream = setup_cast(settings["device"], video_url=video_url,
prep="app", controller=controller)

if stream.is_local_file:
click.echo("Casting local file %s..." % video_url)
click.echo("Playing %s on \"%s\"..." % (stream.video_title, cst.cc_name))
if subtitle is None and dont_load_subtitles_automatically:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason we can't cast remote files with local subtitles as well? It's going to be a bit more tricky, but if it's easy to do, why not?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would not work with save/restore, as we would face the same problem as with save/restore for local files. And if remote files + local subtitles was implemented, users would probably expect this to work.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, alright.

@theychx
Copy link
Collaborator

theychx commented May 6, 2018

@skorokithakis

I'll release 0.7.0 as soon as we merge

Please wait a few days before releasing.

@marcosdiez
Copy link
Contributor Author

I added "requests>=2.0", to setup.py. I got this version because it's the same on pychromecast, so this way the requirement is there but won't cause an extra download

@theychx
Copy link
Collaborator

theychx commented May 7, 2018

@marcosdiez What is the 7653225 commit doing in this PR?

@@ -9,4 +9,5 @@ install: "pip install -r requirements_dev.txt"
# command to run tests, e.g. python setup.py test
script:
- python setup.py test
- flake8 --version
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably unnecessary, the version is printed when installing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can take it out, but for me it was very useful.
I was doing some other development here and flake was telling me other stuff. If you check it out I have PR called flake8 is happy again and another one called Revert "flake8 is happy again", because different flake versions give me different output, so that's the easy way to know what sublibraries flake8 has.

I applied the same trick here https://github.com/jstasiak/python-zeroconf/pulls and that's why I am the only one with a passing build. I would keep it. It does not affect the end user and make the devs happier.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's true, I just generally dislike putting too much stuff in PRs. This is fine though, thanks.

class FileHandler(BaseHTTPRequestHandler):

def format_size(self, size):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very useful debugging information.
Now we get this kind of logs:
10.1.1.3 - - [07/May/2018 11:53:56] "GET /?loaded_from_catt HTTP/1.1" 200 - video/mp4 - 39.80 MB
10.1.1.3 - - [07/May/2018 11:53:56] "GET //tmp/tmpmnfqm1im.vtt HTTP/1.1" 200 - text/vtt;charset=utf-8 - 125.97 KB

I backported from my other PR.

I like knowing how big the file and the mime is useful for the other one anyway.

@skorokithakis
Copy link
Owner

Alright, this looks good to me! @theychx, any feedback/objections?

@theychx
Copy link
Collaborator

theychx commented May 7, 2018

Looks cool! (and passes my testing scheme)

@skorokithakis
Copy link
Owner

Great, approved! Thank you, @marcosdiez!

@mdiez-modus
Copy link

NIIIIIIIIIIIIIIIIIICE !

@skorokithakis skorokithakis merged commit edd22b4 into skorokithakis:master May 7, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Subtitles support
4 participants