forked from jchristman/PyQuicktimeMetaCLI
-
Notifications
You must be signed in to change notification settings - Fork 0
/
meta.py
244 lines (200 loc) · 8.12 KB
/
meta.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
import os
import guessit
import re
#
# Helper class to help infer meta data based on a file name and update an mp4 file with this meta data
#
class Helper(object):
class Keys(object):
TVShow = "show"
TVEpisodeNumber = "episode_sort"
TVSeasonNumber = "season_number"
MovieTitle = "title"
MovieYear = "date"
# TODO: Figure out what ITunes' "Media Kind" is (TVShow / Movie)
def __init__(self, path_to_ffmpeg=None):
self.path_to_ffmpeg = path_to_ffmpeg if not path_to_ffmpeg is None else "/opt/local/bin/ffmpeg"
if not os.path.exists(self.path_to_ffmpeg):
self.path_to_ffmpeg = '/usr/local/bin/ffmpeg'
self.allow_logging = True
if not os.path.exists(self.path_to_ffmpeg):
self.log("[Warning] Failed to locate ffmpeg - Please make sure you have ffmpeg installed on this system")
def log(self, msg):
if self.allow_logging:
print (msg)
#
# MetaData Inferance using Guessit
# (Requires guessit module)
#
def infer_metadata_from_movie_file(self, path_to_file):
guess = guessit.guess_movie_info(path_to_file, info=['filename'])
if guess is None or len(guess) == 0:
self.log("[Warning] Failed to guess movie metadata")
return None
return Helper._extract_metadata_from_guessit_dict(guess, mappings={"title": Helper.Keys.MovieTitle, "year": Helper.Keys.MovieYear})
def infer_metadata_from_tvshow_file(self, path_to_file):
guess = guessit.guess_episode_info(path_to_file, info=['filename'])
if guess is None or len(guess) == 0:
self.log("[Warning] Failed to guess tv show metadata")
return None
return Helper._extract_metadata_from_guessit_dict(guess, mappings={"episodeNumber": Helper.Keys.TVEpisodeNumber, "season": Helper.Keys.TVSeasonNumber, "series": Helper.Keys.TVShow})
@staticmethod
def _extract_metadata_from_guessit_dict(guess, mappings):
d = dict()
for (k, v) in guess.items():
if k in mappings:
if type(v) is int:
d[mappings[k]] = str(v)
else:
d[mappings[k]] = v
return d
#
# Manual Inferance
# (Note: Does not work too well atm)
#
def infer_metadata_from_file(self, path_to_file):
if not os.path.exists(path_to_file):
self.log("[Error] Failed to locate file")
return None
base = os.path.basename(path_to_file)
bex = os.path.splitext(base)
file_name = bex[0]
file_name = Helper.santise_filename(file_name)
regex = self._build_tv_show_regex()
matches = re.match(regex, file_name)
if not matches:
self.log("[Error] Failed to understand file-name")
return None
return self._metadata_from_tv_show_regex_matches(matches)
def _metadata_from_tv_show_regex_matches(self, matches):
details = {}
try:
details[Helper.Keys.TVShow] = matches.group('show_name').replace('.', ' ').strip()
except IndexError:
pass
try:
details[Helper.Keys.TVSeasonNumber] = str(int(matches.group('season')))
except IndexError:
pass
try:
details[Helper.Keys.TVEpisodeNumber] = str(int(matches.group('episode')))
except IndexError:
pass
if len(details) == 0:
self.log("[Warning] Found no meta data for file")
return None
return details
# Based on tvrenamr 3.4.11 function
def _build_tv_show_regex(self, regex=None):
"""Builds the regular expression to extract a files details.
Custom syntax can be used in the regular expression to help specify
parts of the episode's file name. These custom syntax snippets are
replaced by the regular expression blocks show.
%n - [\w\s.,_-]+ - The show name.
%s - \d{1,2} - The season number.
%e - \d{2} - The episode number.
"""
series = r"(?P<show_name>[\w\s.',_-]+)"
season = r"(?P<season>\d{1,2})"
episode = r"(?P<episode>\d{2})"
second_episode = r".E?(?P<episode2>\d{2})*"
if regex is None:
# Build default regex
return series + r"\.[Ss]?" + season + r"[XxEe]?" + episode + second_episode
# series name
regex = regex.replace('%n', series)
# season number
# %s{n}
if '%s{' in regex:
self.log('Season digit number found')
r = regex.split('%s{')[1][:1]
self.log('Specified {0} season digits'.format(r))
s = season.replace('1,2', r)
regex = regex.replace('%s{' + r + '}', s)
self.log('Season regex set: {0}'.format(s))
# %s
if '%s' in regex:
regex = regex.replace('%s', season)
self.log('Default season regex set: {0}'.format(regex))
# episode number
# %e{n}
if '%e{' in regex:
self.log('User set episode digit number found')
r = regex.split('%e{')[1][:1]
self.log('User specified {0} episode digits'.format(r))
e = episode.replace('2', r)
regex = regex.replace('%e{' + r + '}', e)
self.log('Episode regex set: {0}'.format(e))
# %e
if '%e' in regex:
regex = regex.replace('%e', episode)
self.log('Default episode regex set: {0}'.format(regex))
return regex
#
# FFMpeg
#
def _execute_ffmpeg(self, params):
cmd = "%s %s" % (self.path_to_ffmpeg, params)
os.system(cmd)
def show(self, path_to_file):
self._execute_ffmpeg('-i "%s"' % path_to_file)
#
# Utility Methods
#
@staticmethod
# Based on tvrenamr 3.4.11 function
def santise_filename(filename):
"""
Remove bits of the filename that cause a problem.
Initially added to deal specifically with the issues 720[p] causes
in filenames by appearing before or after the season/episode block.
"""
items = (
('_', '.'),
(' ', '.'),
('.720p', ''),
('.720', ''),
('.1080p', ''),
('.1080', ''),
('.H.264', ''),
('.h.264', ''),
('.x264', ''),
)
for target, replacement in items:
filename = filename.replace(target, replacement)
print filename
return filename
@staticmethod
def replace_file_but_stick_original_in_trash(path_to_original, path_to_replacement):
trash_path = os.path.expanduser("~/.Trash")
cmd = 'mv "%s" "%s"' % (path_to_original, trash_path)
os.system(cmd)
cmd = 'mv "%s" "%s"' % (path_to_replacement, path_to_original)
os.system(cmd)
#
# Metadata Setters
# (Requires ffmpeg to work)
#
@staticmethod
def _destination_path_from_input(path_to_file, destination_file):
if destination_file is not None:
o_file = destination_file
else:
ex = os.path.splitext(path_to_file)
o_file = ex[0] + "_meta_tmp" + ex[1]
return o_file
def set_metadata_with_key_and_value(self, path_to_file, key, value, destination_file=None):
o_file = Helper._destination_path_from_input(path_to_file, destination_file)
params = '-i "%s" -metadata %s="%s" -y -codec copy "%s"' % (path_to_file, key, value, o_file)
self._execute_ffmpeg(params)
if destination_file is None:
Helper.replace_file_but_stick_original_in_trash(path_to_file, o_file)
def set_metadata_with_dict(self, path_to_file, meta_dict, destination_file=None):
o_file = Helper._destination_path_from_input(path_to_file, destination_file)
meta_datas = ""
for (k, v) in meta_dict.items():
meta_datas += '-metadata %s="%s" ' % (k, v)
params = '-i "%s" %s -y -codec copy "%s"' % (path_to_file, meta_datas, o_file)
self._execute_ffmpeg(params)
if destination_file is None:
Helper.replace_file_but_stick_original_in_trash(path_to_file, o_file)