Remove a few calls do document.createElement
[armadillo.git] / src / tv_rename.go
1 //
2 // Armadillo File Manager
3 // Copyright (c) 2011, Robert Sesek <http://www.bluestatic.org>
4 //
5 // This program is free software: you can redistribute it and/or modify it under
6 // the terms of the GNU General Public License as published by the Free Software
7 // Foundation, either version 3 of the License, or any later version.
8 //
9
10 package tv_rename
11
12 import (
13 "bufio"
14 "fmt"
15 "http"
16 "net"
17 "os"
18 "path"
19 "regexp"
20 "strconv"
21 "strings"
22 )
23
24 // Takes a full file path and renames the last path component as if it were a
25 // TV episode. This performs the actual rename as well.
26 func RenameEpisode(inPath string) (*string, os.Error) {
27 // Parse the filename into its components.
28 dirName, fileName := path.Split(inPath)
29 info := parseEpisodeName(fileName)
30 if info == nil {
31 return nil, os.NewError("Could not parse file name")
32 }
33
34 // Create the URL and perform the lookup.
35 queryURL := buildURL(info)
36 response, err := performLookup(queryURL)
37 if err != nil {
38 return nil, err
39 }
40
41 // Parse the response into the fullEpisodeInfo struct.
42 fullInfo := parseResponse(response)
43
44 // Create the new path.
45 newName := fmt.Sprintf("%s - %dx%02d - %s", fullInfo.episode.showName,
46 fullInfo.episode.season, fullInfo.episode.episode, fullInfo.episodeName)
47 newName = strings.Replace(newName, "/", "_", -1)
48 newName += path.Ext(fileName)
49 newPath := path.Join(dirName, newName)
50
51 return &newPath, nil
52 }
53
54 type episodeInfo struct {
55 showName string
56 season int
57 episode int
58 }
59
60 type fullEpisodeInfo struct {
61 episode episodeInfo
62 episodeName string
63 }
64
65 // Parses the last path component into a the component structure.
66 func parseEpisodeName(name string) *episodeInfo {
67 regex := regexp.MustCompile("(.+)( |\\.)[sS]?([0-9]+)[xeXE]([0-9]+)")
68 matches := regex.FindAllStringSubmatch(name, -1)
69 if len(matches) < 1 || len(matches[0]) < 4 {
70 return nil
71 }
72
73 // Convert the season and episode numbers to integers.
74 season, episode := convertEpisode(matches[0][3], matches[0][4])
75 if season == 0 && season == episode {
76 return nil
77 }
78
79 // If the separator between the show title and episode is a period, then
80 // it's likely of the form "some.show.name.s03e06.720p.blah.mkv", so strip the
81 // periods in the title.
82 var showName string = matches[0][1]
83 if matches[0][2] == "." {
84 showName = strings.Replace(matches[0][1], ".", " ", -1)
85 }
86
87 return &episodeInfo{
88 showName,
89 season,
90 episode,
91 }
92 }
93
94 // Builds the URL to which we send a HTTP request to get the episode name.
95 func buildURL(info *episodeInfo) string {
96 return fmt.Sprintf("http://services.tvrage.com/tools/quickinfo.php?show=%s&ep=%dx%d",
97 http.URLEscape(info.showName), info.season, info.episode)
98 }
99
100 // Converts a season and episode to integers. If the return values are both 0,
101 // an error occurred.
102 func convertEpisode(season string, episode string) (int, int) {
103 seasonInt, err := strconv.Atoi(season)
104 if err != nil {
105 return 0, 0
106 }
107 episodeInt, err := strconv.Atoi(episode)
108 if err != nil {
109 return 0, 0
110 }
111 return seasonInt, episodeInt
112 }
113
114 // Performs the actual lookup and returns the HTTP response.
115 func performLookup(urlString string) (*http.Response, os.Error) {
116 url, err := http.ParseURL(urlString)
117 if err != nil {
118 return nil, err
119 }
120
121 // Open a TCP connection.
122 conn, err := net.Dial("tcp", url.Host+":"+url.Scheme)
123 if err != nil {
124 return nil, err
125 }
126
127 // Perform the HTTP request.
128 client := http.NewClientConn(conn, nil)
129 var request http.Request
130 request.URL = url
131 request.Method = "GET"
132 request.UserAgent = "Armadillo File Manager"
133 err = client.Write(&request)
134 if err != nil {
135 return nil, err
136 }
137 return client.Read(&request)
138 }
139
140 // Parses the HTTP response from performLookup().
141 func parseResponse(response *http.Response) *fullEpisodeInfo {
142 var err os.Error
143 var line string
144 var info fullEpisodeInfo
145
146 buf := bufio.NewReader(response.Body)
147 for ; err != os.EOF; line, err = buf.ReadString('\n') {
148 // An error ocurred while reading.
149 if err != nil {
150 return nil
151 }
152 var parts []string = strings.Split(line, "@", 2)
153 if len(parts) != 2 {
154 continue
155 }
156 switch parts[0] {
157 case "Show Name":
158 info.episode.showName = strings.TrimSpace(parts[1])
159 case "Episode Info":
160 // Split the line, which is of the form: |SxE^Name^AirDate|.
161 parts = strings.Split(parts[1], "^", 3)
162 info.episodeName = parts[1]
163 // Split the episode string.
164 episode := strings.Split(parts[0], "x", 2)
165 info.episode.season, info.episode.episode = convertEpisode(episode[0], episode[1])
166 if info.episode.season == 0 && info.episode.season == info.episode.episode {
167 return nil
168 }
169 }
170 }
171 return &info
172 }