Skip to content
This repository was archived by the owner on Jan 23, 2025. It is now read-only.

Commit fdb720b

Browse files
authored
Merge pull request #7 from oPromessa/example
Usage example search_check.py to generate/check vsmeta files.
2 parents 876049b + 41cef28 commit fdb720b

File tree

2 files changed

+341
-1
lines changed

2 files changed

+341
-1
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Example: The video is called `video.mpg`, the metadata file shall be named `vide
1919
## How to use the code
2020

2121
Here's an example piece of code. Other examples can be found in the unit test classes.
22+
* see also [search_check.py](doc/search_check.py)
2223

2324
```python:
2425
@@ -113,7 +114,18 @@ Here are the screenshots of the supported media types in German English (I did n
113114

114115
You're welcome to contribute, but please be aware of ...
115116

116-
* You should provide a unit test for your changes with a good coverage. To do this, install ``nose2`` and ``coverage`` and execute the tests with
117+
* You should provide a unit test for your changes with a good coverage. To do this, install ``nose2`` and ``coverage`` and execute the tests with:
118+
```sh
119+
$ python -m venv venv
120+
$ source venv/bin/activate
121+
$ pip install nose2
122+
$ pip install coverage
123+
$ nose2 --verbose
124+
test_encodeTemplate2 (testvsmetaSeriesEncoder.TestVsMetaEncoder.test_encodeTemplate2) ... ok
125+
test_encodeTemplate3 (testvsmetaSeriesEncoder.TestVsMetaEncoder.test_encodeTemplate3) ... ok
126+
test_encodeTemplate4 (testvsmetaSeriesEncoder.TestVsMetaEncoder.test_encodeTemplate4) ... ok
127+
(...)
128+
```
117129
* I won't react immediately with checking your pull requests in.
118130

119131
# References

doc/search_check.py

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
"""
2+
by oPromessa, 2024
3+
4+
Example use of vsMetaFileEncoder.
5+
* Generates vsmeta from seraching on IMDb.
6+
* Also checks vsmeta file and shows its contents.
7+
8+
Makes use of modules: click, requests, idmbmovies
9+
10+
To use, install required modules with pip:
11+
pip install click
12+
pip install requests
13+
pip install imdbmovies
14+
"""
15+
import os
16+
import re
17+
18+
from datetime import date, datetime
19+
import textwrap
20+
21+
import click
22+
import requests
23+
24+
from imdbmovies import IMDB
25+
26+
from vsmetaCodec.vsmetaEncoder import VsMetaMovieEncoder
27+
from vsmetaCodec.vsmetaDecoder import VsMetaDecoder
28+
from vsmetaCodec.vsmetaInfo import VsMetaImageInfo
29+
30+
31+
def write_vsmeta_file(filename: str, content: bytes):
32+
""" Writes to file in binary mode. Used to write .vsmeta files.
33+
"""
34+
with open(filename, 'wb') as write_file:
35+
write_file.write(content)
36+
write_file.close()
37+
38+
39+
def read_vsmeta_file(filename: str) -> bytes:
40+
""" Reads from file in binary mode. Used to read .vsmeta files.
41+
"""
42+
with open(filename, 'rb') as read_file:
43+
file_content = read_file.read()
44+
read_file.close()
45+
return file_content
46+
47+
48+
def lookfor_imdb(movie_title, year=None, tv=False):
49+
""" Returns movie_info of first movie from year returned by search in IMDb.
50+
"""
51+
52+
imdb = IMDB()
53+
results = imdb.search(movie_title, year=year, tv=tv)
54+
55+
# Filter only movie type entries
56+
movie_results = [result for result in results["results"]
57+
if result["type"] == "movie"]
58+
59+
print(
60+
f"Found: [{len(movie_results)}] entries for "
61+
f"Title: [{movie_title}] Year: [{year}]"
62+
)
63+
64+
for cnt, mv in enumerate(movie_results):
65+
print(
66+
f"\tEntry: [{cnt}] Name: [{click.style(mv['name'], fg='yellow')}] "
67+
f"Id: [{mv['id']}] Type: [{mv['type']}]")
68+
69+
if movie_results:
70+
movie_info = imdb.get_by_id(movie_results[0]['id'])
71+
return movie_results[0]['id'], movie_info
72+
73+
return None, None
74+
75+
76+
def download_poster(url, filename):
77+
""" Downloads Image from URL into a JPG file.
78+
"""
79+
http_timeout = 15
80+
81+
response = requests.get(url, timeout=http_timeout)
82+
if response.status_code == 200:
83+
with open(filename, 'wb') as f:
84+
f.write(response.content)
85+
86+
87+
def find_metadata(title, year, filename, verbose):
88+
"""Search for a movie/Year metada on IMDb.
89+
90+
If found, downloads to a local .JPG file the poster
91+
"""
92+
print(
93+
f"-------------- : Processing title [{click.style(title, fg='green')}] "
94+
f"year [{year}] filename [{filename}]")
95+
96+
vsmeta_filename = None
97+
98+
year = None if year is None else int(year)
99+
100+
# Search IMDB for movie information
101+
movie_id, movie_info = lookfor_imdb(title, year=year)
102+
103+
if movie_id and movie_info:
104+
# Download poster
105+
poster_url = movie_info['poster']
106+
poster_filename = f'{title.replace(" ", "_")}_poster.jpg'
107+
download_poster(poster_url, poster_filename)
108+
109+
# Map IMDB fields to VSMETA
110+
# and Encode VSMETA
111+
vsmeta_filename = filename + ".vsmeta"
112+
map_to_vsmeta(movie_id, movie_info, poster_filename,
113+
vsmeta_filename, verbose)
114+
else:
115+
print(f"No information found for '{click.style(title, fg='red')}'")
116+
117+
print(
118+
f"\tProcessed title [{click.style(title, fg='green')}] year [{year}] "
119+
f"vsmeta [{vsmeta_filename}]")
120+
121+
return vsmeta_filename
122+
123+
124+
def map_to_vsmeta(imdb_id, imdb_info, poster_file, vsmeta_filename, verbose):
125+
"""Encodes a .VSMETA file based on imdb_info and poster_file """
126+
127+
# vsmetaMovieEncoder
128+
vsmeta_writer = VsMetaMovieEncoder()
129+
130+
# Build up vsmeta info
131+
info = vsmeta_writer.info
132+
133+
# Title
134+
info.showTitle = imdb_info['name']
135+
info.showTitle2 = imdb_info['name']
136+
# Tag line
137+
info.episodeTitle = f"{imdb_info['name']}"
138+
139+
# Publishing Date
140+
info.setEpisodeDate(date(
141+
int(imdb_info['datePublished'][:4]),
142+
int(imdb_info['datePublished'][5:7]),
143+
int(imdb_info['datePublished'][8:])))
144+
145+
# Set to 0 for Movies: season and episode
146+
info.season = 0
147+
info.episode = 0
148+
149+
# Not used. Set to 1900-01-01
150+
info.tvshowReleaseDate = date(1900, 1, 1)
151+
152+
# Locked = False
153+
info.episodeLocked = False
154+
155+
info.timestamp = int(datetime.now().timestamp())
156+
157+
# Classification
158+
# A classification of None would crash the reading of .vsmeta file with error
159+
info.classification = "" if imdb_info['contentRating'] is None else imdb_info['contentRating']
160+
161+
# Rating
162+
info.rating = imdb_info['rating']['ratingValue']
163+
164+
# Summary
165+
info.chapterSummary = imdb_info['description']
166+
167+
# Cast
168+
info.list.cast = []
169+
for actor in imdb_info['actor']:
170+
info.list.cast.append(actor['name'])
171+
172+
# Director
173+
info.list.director = []
174+
for director in imdb_info['director']:
175+
info.list.director.append(director['name'])
176+
177+
# Writer
178+
info.list.writer = []
179+
for creator in imdb_info['creator']:
180+
info.list.writer.append(creator['name'])
181+
182+
# Genre
183+
info.list.genre = imdb_info['genre']
184+
185+
# Read JPG images for Poster and Background
186+
with open(poster_file, "rb") as image:
187+
f = image.read()
188+
189+
# Poster (of Movie)
190+
episode_img = VsMetaImageInfo()
191+
episode_img.image = f
192+
info.episodeImageInfo.append(episode_img)
193+
194+
# Background (of Movie)
195+
# Use Posters file for Backdrop also
196+
info.backdropImageInfo.image = f
197+
198+
# Not used. Set to VsImageIfnfo()
199+
info.posterImageInfo = episode_img
200+
201+
if verbose:
202+
print("\t---------------: ---------------")
203+
print(f"\tIMDB id : {imdb_id}")
204+
print(f"\tTitle : {info.showTitle}")
205+
print(f"\tTitle2 : {info.showTitle2}")
206+
print(f"\tEpisode title : {info.episodeTitle}")
207+
print(f"\tEpisode year : {info.year}")
208+
print(f"\tEpisode date : {info.episodeReleaseDate}")
209+
print(f"\tEpisode locked : {info.episodeLocked}")
210+
print(f"\tTimeStamp : {info.timestamp}")
211+
print(f"\tClassification : {info.classification}")
212+
print(f"\tRating : {info.rating:1.1f}")
213+
wrap_text = "\n\t ".join(
214+
textwrap.wrap(info.chapterSummary, 150))
215+
print(f"\tSummary : {wrap_text}")
216+
print(
217+
f"\tCast : {''.join([f'{name}, ' for name in info.list.cast])}")
218+
print(
219+
f"\tDirector : {''.join([f'{name}, ' for name in info.list.director])}")
220+
print(
221+
f"\tWriter : {''.join([f'{name}, ' for name in info.list.writer])}")
222+
print(
223+
f"\tGenre : {''.join([f'{name}, ' for name in info.list.genre])}")
224+
print("\t---------------: ---------------")
225+
226+
write_vsmeta_file(vsmeta_filename, vsmeta_writer.encode(info))
227+
228+
return True
229+
230+
231+
def find_files(root_dir, valid_ext=(".mp4", ".mkv", ".avi", ".mpg")):
232+
""" Returns files with extension in valid_ext list
233+
"""
234+
235+
for root, _, files in os.walk(root_dir):
236+
for file in files:
237+
if any(file.casefold().endswith(ext) for ext in valid_ext) and \
238+
not os.path.isdir(os.path.join(root, file)):
239+
yield os.path.join(root, file)
240+
241+
242+
def extract_info(file_path):
243+
""" Convert file_path into dirname and from basename extract
244+
movie_tile and year. Expecting filename format 'movie title name (1999)'
245+
"""
246+
dirname = os.path.dirname(file_path)
247+
basename = os.path.basename(file_path)
248+
249+
# filtered_value = re.search(r'\D*(\d{4})', basename)
250+
filtered_value = re.search(r'^(.*?)(\d{4})(.*)$', basename)
251+
filtered_title = None
252+
filtered_year = None
253+
if filtered_value:
254+
filtered_title = filtered_value.group(1)
255+
filtered_year = filtered_value.group(2)
256+
else:
257+
filtered_title = basename
258+
259+
filtered_title = filtered_title.replace('.', ' ').strip()
260+
261+
return dirname, basename, filtered_title, filtered_year
262+
263+
264+
def check_file(file_path):
265+
"""Read .vsmeta file and print it's contents.
266+
267+
Images within .vsmeta are saved as image_back_drop.jpg and image_poster_NN.jpg
268+
When checking multiple files, these files are overwritten.
269+
"""
270+
271+
vsmeta_bytes = read_vsmeta_file(file_path)
272+
reader = VsMetaDecoder()
273+
reader.decode(vsmeta_bytes)
274+
275+
reader.info.printInfo('.', prefix=os.path.basename(file_path))
276+
277+
278+
@click.command()
279+
@click.option('--search',
280+
type=click.Path(exists=True,
281+
file_okay=False,
282+
dir_okay=True,
283+
resolve_path=True),
284+
help="Folder to recursively search for media files to be processed into .vsmeta.")
285+
@click.option("--check",
286+
type=click.Path(exists=True,
287+
file_okay=True,
288+
dir_okay=True,
289+
resolve_path=True),
290+
help="Check .vsmeta files. Show info. "
291+
"Exclusive with --search option.")
292+
@click.option('-v', '--verbose', is_flag=True,
293+
help="Shows info found on IMDB.")
294+
def main(search, check, verbose):
295+
"""Searches on a folder for Movie Titles and generates .vsmeta files.
296+
You can then copy them over to your Library.
297+
"""
298+
299+
if not (check or search) or (check and search):
300+
raise SystemExit(
301+
"Must specify at least one (and exclusively) option "
302+
"--search or --check. Use --help for additional help.")
303+
304+
if check:
305+
if os.path.isfile(check) and check.endswith(".vsmeta"):
306+
print(f"-------------- : Checking file [{check}]")
307+
check_file(check)
308+
elif os.path.isdir(check):
309+
for found_file in find_files(check, valid_ext=('.vsmeta', )):
310+
print(f"-------------- : Checking file [{check}]")
311+
check_file(found_file)
312+
else:
313+
raise print(
314+
"Invalid check path or file name. "
315+
"sPlease provide a valid directory or .vsmeta file.")
316+
317+
if search:
318+
print(f"Processing folder: [{search}].")
319+
320+
# Iterate over the matching files
321+
for found_file in find_files(search):
322+
# print(f"Found file: [{found_file}]")
323+
_, basename, title, year = extract_info(found_file)
324+
find_metadata(title, year, basename, verbose)
325+
326+
327+
if __name__ == "__main__":
328+
main()

0 commit comments

Comments
 (0)